55 * ------------------------------------------------
66 *
77 * This module implements the Coupling Coordination Degree (CCD) framework,
8- * a widely used approach for measuring the interaction, coupling strength,
8+ * a widely used method for measuring the interaction, coupling strength,
99 * and coordinated development among multiple subsystems or indicators.
1010 *
1111 * The CCD model evaluates the internal consistency of a system composed of
142142 * Notes
143143 * ---------------------------------------------------------------------------
144144 *
145- * • All computations are performed independently for each spatial unit .
145+ * • Input values MUST be normalized to [0, 1] or be on a comparable scale .
146146 *
147- * • Input values are assumed to be non-negative. For the standard model ,
148- * strictly positive values are required due to the geometric mean .
147+ * • If all indicators are zero (i.e., no subsystem development) ,
148+ * the coupling degree (C) is defined as 0 .
149149 *
150- * • Numerical safeguards may be required to avoid:
151- * - division by zero
152- * - negative values under square root due to floating-point errors
150+ * This avoids undefined operations (e.g., 0/0) and ensures
151+ * that systems without development are not interpreted as
152+ * having perfect coupling.
153153 *
154- * • The Wang formulation uses pairwise absolute differences as a measure
155- * of dispersion.
154+ * • The CCD model is scale-sensitive:
155+ * - Standard model depends on relative magnitudes
156+ * - Wang model depends on pairwise differences and max-normalization
157+ * - Fan model is variance-based
156158 *
157- * • The ccd_c_single function is useful when computing C for a single
158- * observation without constructing a full matrix.
159+ * • Without normalization, the coupling degree (C) may not reflect
160+ * coordination but instead reflect scale dominance.
161+ *
162+ * • Recommended preprocessing:
163+ * - Min-max normalization
164+ * - Z-score normalization (followed by rescaling to [0,1] if needed)
159165 *
160166 * ---------------------------------------------------------------------------
161167 * Author: Wenbo Lyu (Github: @SpatLyu)
171177#include < numeric>
172178#include < algorithm>
173179#include < stdexcept>
180+ #include < RcppThread.h>
174181
175182namespace coupling
176183{
@@ -204,6 +211,10 @@ inline double ccd_c_single(
204211
205212 double geo_mean = std::pow (prod_sum, 1.0 /p);
206213 double arith_mean = mean (vec);
214+
215+ if (coupling::numericutils::doubleNearlyEqual (arith_mean, 0.0 )) {
216+ return 0.0 ;
217+ }
207218
208219 C_val = geo_mean / arith_mean;
209220 }
@@ -226,6 +237,10 @@ inline double ccd_c_single(
226237
227238 double max_u = *std::max_element (vec.begin (), vec.end ());
228239
240+ if (coupling::numericutils::doubleNearlyEqual (max_u, 0.0 )) {
241+ return 0.0 ;
242+ }
243+
229244 double prod = 1.0 ;
230245 for (double u : vec) {
231246 prod *= (u / max_u);
@@ -240,13 +255,18 @@ inline double ccd_c_single(
240255 // fan
241256 // =========================
242257 else if (method == " fan" ) {
243-
244258 double sum_u = std::accumulate (vec.begin (), vec.end (), 0.0 );
259+ // if (coupling::numericutils::doubleNearlyEqual(sum_u, 0.0)) {
260+ // return 0.0;
261+ // }
245262
246263 double sum_u2 = 0.0 ;
247264 for (double u : vec) {
248265 sum_u2 += u * u;
249266 }
267+ // if (coupling::numericutils::doubleNearlyEqual(sum_u2, 0.0)) {
268+ // return 0.0;
269+ // }
250270
251271 double numerator = p * sum_u2 - sum_u * sum_u;
252272 double denom = p * p;
@@ -261,92 +281,27 @@ inline double ccd_c_single(
261281 throw std::invalid_argument (" Unknown method" );
262282 }
263283
264- return C_val;
284+ return std::clamp ( C_val, 0.0 , 1.0 ) ;
265285}
266286
267287inline std::vector<double > ccd_c (
268288 const std::vector<std::vector<double >>& mat,
269- const std::string& method = " standard"
289+ const std::string& method = " standard" ,
290+ size_t threads = 1
270291) {
271292 size_t n_units = mat.size ();
272293 if (n_units == 0 ) return {};
273294
274- size_t p = mat[0 ].size (); // number of U values per unit
275-
276295 std::vector<double > result (n_units, 0.0 );
277-
278- for (size_t i = 0 ; i < n_units; ++i) {
279- const std::vector<double >& U = mat[i];
280-
281- // =========================
282- // standard
283- // =========================
284- if (method == " standard" ) {
285- double prod_sum = 1.0 ;
286- for (double u : U) {
287- // if (u <= 0) throw std::runtime_error("Values must be positive.");
288- prod_sum *= u;
289- }
290-
291- double geo_mean = std::pow (prod_sum, 1.0 /p);
292- double arith_mean = mean (U);
293-
294- result[i] = geo_mean / arith_mean;
295- }
296-
297- // =========================
298- // wang
299- // =========================
300- else if (method == " wang" ) {
301-
302- double sum_dist = 0.0 ;
303-
304- for (size_t j = 0 ; j < p - 1 ; ++j) {
305- for (size_t k = j + 1 ; k < p; ++k) {
306- sum_dist += std::abs (U[j] - U[k]);
307- }
308- }
309-
310- double denom = (p - 1 ) * p / 2.0 ;
311- double term1 = 1.0 - (sum_dist / denom);
312- if (term1 < 0 ) term1 = 0 ;
313-
314- double max_u = *std::max_element (U.begin (), U.end ());
315-
316- double prod = 1.0 ;
317- for (double u : U) {
318- prod *= (u / max_u);
319- }
320-
321- double term2 = std::pow (prod, 1.0 / (p - 1 ));
322-
323- result[i] = std::sqrt (term1 * term2);
324- }
325-
326- // =========================
327- // fan
328- // =========================
329- else if (method == " fan" ) {
330-
331- double sum_u = std::accumulate (U.begin (), U.end (), 0.0 );
332-
333- double sum_u2 = 0.0 ;
334- for (double u : U) {
335- sum_u2 += u * u;
336- }
337-
338- double numerator = p * sum_u2 - sum_u * sum_u;
339- double denom = p * p;
340-
341- double val = numerator / denom;
342- if (val < 0 ) val = 0 ;
343-
344- result[i] = 1.0 - 2.0 * std::sqrt (val);
345- }
346-
347- else {
348- throw std::invalid_argument (" Unknown method" );
296+
297+ if (threads <= 1 ) {
298+ for (size_t i = 0 ; i < n_units; ++i) {
299+ result[i] = ccd_c_single (mat[i], method);
349300 }
301+ } else {
302+ RcppThread::parallelFor (0 , n_units, [&](size_t i) {
303+ result[i] = ccd_c_single (mat[i], method);
304+ }, threads);
350305 }
351306
352307 return result;
@@ -355,14 +310,15 @@ inline std::vector<double> ccd_c(
355310inline std::vector<std::vector<double >> ccd (
356311 const std::vector<std::vector<double >>& mat,
357312 const std::vector<double >& weight,
358- const std::string& method = " standard"
313+ const std::string& method = " standard" ,
314+ size_t threads = 1
359315) {
360316 size_t n_units = mat.size (); // number of unit
361317 size_t p = mat[0 ].size (); // number of U values per unit
362318
363319 std::vector<std::vector<double >> result (2 , std::vector<double >(n_units, 0.0 ));
364320
365- std::vector<double > C_vals = ccd_c (mat, method); // C values
321+ std::vector<double > C_vals = ccd_c (mat, method, threads ); // C values
366322 result[0 ] = C_vals;
367323
368324 for (size_t i = 0 ; i < n_units; ++i) {
0 commit comments