GNU Octave  4.4.1
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
oct-norm.cc
Go to the documentation of this file.
1 /*
2 
3 Copyright (C) 2008-2018 VZLU Prague, a.s.
4 
5 This file is part of Octave.
6 
7 Octave is free software: you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11 
12 Octave is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Octave; see the file COPYING. If not, see
19 <https://www.gnu.org/licenses/>.
20 
21 */
22 
23 // author: Jaroslav Hajek <highegg@gmail.com>
24 
25 #if defined (HAVE_CONFIG_H)
26 # include "config.h"
27 #endif
28 
29 #include <cmath>
30 
31 #include <algorithm>
32 #include <limits>
33 #include <vector>
34 
35 #include "Array.h"
36 #include "CColVector.h"
37 #include "CMatrix.h"
38 #include "CRowVector.h"
39 #include "CSparse.h"
40 #include "MArray.h"
41 #include "dColVector.h"
42 #include "dDiagMatrix.h"
43 #include "dMatrix.h"
44 #include "dRowVector.h"
45 #include "dSparse.h"
46 #include "fCColVector.h"
47 #include "fCMatrix.h"
48 #include "fCRowVector.h"
49 #include "fColVector.h"
50 #include "fDiagMatrix.h"
51 #include "fMatrix.h"
52 #include "fRowVector.h"
53 #include "lo-error.h"
54 #include "lo-ieee.h"
55 #include "lo-mappers.h"
56 #include "mx-cm-s.h"
57 #include "mx-fcm-fs.h"
58 #include "mx-fs-fcm.h"
59 #include "mx-s-cm.h"
60 #include "oct-cmplx.h"
61 #include "quit.h"
62 #include "svd.h"
63 
64 // Theory: norm accumulator is an object that has an accum method able
65 // to handle both real and complex element, and a cast operator
66 // returning the intermediate norm. Reference: Higham, N. "Estimating
67 // the Matrix p-Norm." Numer. Math. 62, 539-555, 1992.
68 
69 // norm accumulator for the p-norm
70 template <typename R>
72 {
73  R p,scl,sum;
74 public:
75  norm_accumulator_p () { } // we need this one for Array
76  norm_accumulator_p (R pp) : p(pp), scl(0), sum(1) { }
77 
78  template <typename U>
79  void accum (U val)
80  {
81  octave_quit ();
82  R t = std::abs (val);
83  if (scl == t) // we need this to handle Infs properly
84  sum += 1;
85  else if (scl < t)
86  {
87  sum *= std::pow (scl/t, p);
88  sum += 1;
89  scl = t;
90  }
91  else if (t != 0)
92  sum += std::pow (t/scl, p);
93  }
94  operator R () { return scl * std::pow (sum, 1/p); }
95 };
96 
97 // norm accumulator for the minus p-pseudonorm
98 template <typename R>
100 {
101  R p,scl,sum;
102 public:
103  norm_accumulator_mp () { } // we need this one for Array
104  norm_accumulator_mp (R pp) : p(pp), scl(0), sum(1) { }
105 
106  template <typename U>
107  void accum (U val)
108  {
109  octave_quit ();
110  R t = 1 / std::abs (val);
111  if (scl == t)
112  sum += 1;
113  else if (scl < t)
114  {
115  sum *= std::pow (scl/t, p);
116  sum += 1;
117  scl = t;
118  }
119  else if (t != 0)
120  sum += std::pow (t/scl, p);
121  }
122  operator R () { return scl * std::pow (sum, -1/p); }
123 };
124 
125 // norm accumulator for the 2-norm (euclidean)
126 template <typename R>
128 {
129  R scl,sum;
130  static R pow2 (R x) { return x*x; }
131 public:
132  norm_accumulator_2 () : scl(0), sum(1) { }
133 
134  void accum (R val)
135  {
136  R t = std::abs (val);
137  if (scl == t)
138  sum += 1;
139  else if (scl < t)
140  {
141  sum *= pow2 (scl/t);
142  sum += 1;
143  scl = t;
144  }
145  else if (t != 0)
146  sum += pow2 (t/scl);
147  }
148 
149  void accum (std::complex<R> val)
150  {
151  accum (val.real ());
152  accum (val.imag ());
153  }
154 
155  operator R () { return scl * std::sqrt (sum); }
156 };
157 
158 // norm accumulator for the 1-norm (city metric)
159 template <typename R>
161 {
162  R sum;
163 public:
164  norm_accumulator_1 () : sum (0) { }
165  template <typename U>
166  void accum (U val)
167  {
168  sum += std::abs (val);
169  }
170  operator R () { return sum; }
171 };
172 
173 // norm accumulator for the inf-norm (max metric)
174 template <typename R>
176 {
177  R max;
178 public:
180  template <typename U>
181  void accum (U val)
182  {
183  if (octave::math::isnan (val))
185  else
186  max = std::max (max, std::abs (val));
187  }
188  operator R () { return max; }
189 };
190 
191 // norm accumulator for the -inf pseudonorm (min abs value)
192 template <typename R>
194 {
195  R min;
196 public:
197  norm_accumulator_minf () : min (octave::numeric_limits<R>::Inf ()) { }
198  template <typename U>
199  void accum (U val)
200  {
201  if (octave::math::isnan (val))
203  else
204  min = std::min (min, std::abs (val));
205  }
206  operator R () { return min; }
207 };
208 
209 // norm accumulator for the 0-pseudonorm (hamming distance)
210 template <typename R>
212 {
213  unsigned int num;
214 public:
215  norm_accumulator_0 () : num (0) { }
216  template <typename U>
217  void accum (U val)
218  {
219  if (val != static_cast<U> (0)) ++num;
220  }
221  operator R () { return num; }
222 };
223 
224 // OK, we're armed :) Now let's go for the fun
225 
226 template <typename T, typename R, typename ACC>
227 inline void vector_norm (const Array<T>& v, R& res, ACC acc)
228 {
229  for (octave_idx_type i = 0; i < v.numel (); i++)
230  acc.accum (v(i));
231 
232  res = acc;
233 }
234 
235 // dense versions
236 template <typename T, typename R, typename ACC>
237 void column_norms (const MArray<T>& m, MArray<R>& res, ACC acc)
238 {
239  res = MArray<R> (dim_vector (1, m.columns ()));
240  for (octave_idx_type j = 0; j < m.columns (); j++)
241  {
242  ACC accj = acc;
243  for (octave_idx_type i = 0; i < m.rows (); i++)
244  accj.accum (m(i, j));
245 
246  res.xelem (j) = accj;
247  }
248 }
249 
250 template <typename T, typename R, typename ACC>
251 void row_norms (const MArray<T>& m, MArray<R>& res, ACC acc)
252 {
253  res = MArray<R> (dim_vector (m.rows (), 1));
254  std::vector<ACC> acci (m.rows (), acc);
255  for (octave_idx_type j = 0; j < m.columns (); j++)
256  {
257  for (octave_idx_type i = 0; i < m.rows (); i++)
258  acci[i].accum (m(i, j));
259  }
260 
261  for (octave_idx_type i = 0; i < m.rows (); i++)
262  res.xelem (i) = acci[i];
263 }
264 
265 // sparse versions
266 template <typename T, typename R, typename ACC>
267 void column_norms (const MSparse<T>& m, MArray<R>& res, ACC acc)
268 {
269  res = MArray<R> (dim_vector (1, m.columns ()));
270  for (octave_idx_type j = 0; j < m.columns (); j++)
271  {
272  ACC accj = acc;
273  for (octave_idx_type k = m.cidx (j); k < m.cidx (j+1); k++)
274  accj.accum (m.data (k));
275 
276  res.xelem (j) = accj;
277  }
278 }
279 
280 template <typename T, typename R, typename ACC>
281 void row_norms (const MSparse<T>& m, MArray<R>& res, ACC acc)
282 {
283  res = MArray<R> (dim_vector (m.rows (), 1));
284  std::vector<ACC> acci (m.rows (), acc);
285  for (octave_idx_type j = 0; j < m.columns (); j++)
286  {
287  for (octave_idx_type k = m.cidx (j); k < m.cidx (j+1); k++)
288  acci[m.ridx (k)].accum (m.data (k));
289  }
290 
291  for (octave_idx_type i = 0; i < m.rows (); i++)
292  res.xelem (i) = acci[i];
293 }
294 
295 // now the dispatchers
296 #define DEFINE_DISPATCHER(FUNC_NAME, ARG_TYPE, RES_TYPE) \
297  template <typename T, typename R> \
298  RES_TYPE FUNC_NAME (const ARG_TYPE& v, R p) \
299  { \
300  RES_TYPE res; \
301  if (p == 2) \
302  FUNC_NAME (v, res, norm_accumulator_2<R> ()); \
303  else if (p == 1) \
304  FUNC_NAME (v, res, norm_accumulator_1<R> ()); \
305  else if (lo_ieee_isinf (p)) \
306  { \
307  if (p > 0) \
308  FUNC_NAME (v, res, norm_accumulator_inf<R> ()); \
309  else \
310  FUNC_NAME (v, res, norm_accumulator_minf<R> ()); \
311  } \
312  else if (p == 0) \
313  FUNC_NAME (v, res, norm_accumulator_0<R> ()); \
314  else if (p > 0) \
315  FUNC_NAME (v, res, norm_accumulator_p<R> (p)); \
316  else \
317  FUNC_NAME (v, res, norm_accumulator_mp<R> (p)); \
318  return res; \
319  }
320 
326 
327 // The approximate subproblem in Higham's method. Find lambda and mu such that
328 // norm ([lambda, mu], p) == 1 and norm (y*lambda + col*mu, p) is maximized.
329 // Real version. As in Higham's paper.
330 template <typename ColVectorT, typename R>
331 static void
332 higham_subp (const ColVectorT& y, const ColVectorT& col,
333  octave_idx_type nsamp, R p, R& lambda, R& mu)
334 {
335  R nrm = 0;
336  for (octave_idx_type i = 0; i < nsamp; i++)
337  {
338  octave_quit ();
339  R fi = i * static_cast<R> (M_PI) / nsamp;
340  R lambda1 = cos (fi);
341  R mu1 = sin (fi);
342  R lmnr = std::pow (std::pow (std::abs (lambda1), p) +
343  std::pow (std::abs (mu1), p), 1/p);
344  lambda1 /= lmnr; mu1 /= lmnr;
345  R nrm1 = vector_norm (lambda1 * y + mu1 * col, p);
346  if (nrm1 > nrm)
347  {
348  lambda = lambda1;
349  mu = mu1;
350  nrm = nrm1;
351  }
352  }
353 }
354 
355 // Complex version. Higham's paper does not deal with complex case, so we use
356 // a simple extension. First, guess the magnitudes as in real version, then
357 // try to rotate lambda to improve further.
358 template <typename ColVectorT, typename R>
359 static void
360 higham_subp (const ColVectorT& y, const ColVectorT& col,
361  octave_idx_type nsamp, R p,
362  std::complex<R>& lambda, std::complex<R>& mu)
363 {
364  typedef std::complex<R> CR;
365  R nrm = 0;
366  lambda = 1.0;
367  CR lamcu = lambda / std::abs (lambda);
368  // Probe magnitudes
369  for (octave_idx_type i = 0; i < nsamp; i++)
370  {
371  octave_quit ();
372  R fi = i * static_cast<R> (M_PI) / nsamp;
373  R lambda1 = cos (fi);
374  R mu1 = sin (fi);
375  R lmnr = std::pow (std::pow (std::abs (lambda1), p) +
376  std::pow (std::abs (mu1), p), 1/p);
377  lambda1 /= lmnr; mu1 /= lmnr;
378  R nrm1 = vector_norm (lambda1 * lamcu * y + mu1 * col, p);
379  if (nrm1 > nrm)
380  {
381  lambda = lambda1 * lamcu;
382  mu = mu1;
383  nrm = nrm1;
384  }
385  }
386  R lama = std::abs (lambda);
387  // Probe orientation
388  for (octave_idx_type i = 0; i < nsamp; i++)
389  {
390  octave_quit ();
391  R fi = i * static_cast<R> (M_PI) / nsamp;
392  lamcu = CR (cos (fi), sin (fi));
393  R nrm1 = vector_norm (lama * lamcu * y + mu * col, p);
394  if (nrm1 > nrm)
395  {
396  lambda = lama * lamcu;
397  nrm = nrm1;
398  }
399  }
400 }
401 
402 // the p-dual element (should work for both real and complex)
403 template <typename T, typename R>
404 inline T elem_dual_p (T x, R p)
405 {
406  return octave::math::signum (x) * std::pow (std::abs (x), p-1);
407 }
408 
409 // the VectorT is used for vectors, but actually it has to be
410 // a Matrix type to allow all the operations. For instance SparseMatrix
411 // does not support multiplication with column/row vectors.
412 // the dual vector
413 template <typename VectorT, typename R>
414 VectorT dual_p (const VectorT& x, R p, R q)
415 {
416  VectorT res (x.dims ());
417  for (octave_idx_type i = 0; i < x.numel (); i++)
418  res.xelem (i) = elem_dual_p (x(i), p);
419  return res / vector_norm (res, q);
420 }
421 
422 // Higham's hybrid method
423 template <typename MatrixT, typename VectorT, typename R>
424 R higham (const MatrixT& m, R p, R tol, int maxiter,
425  VectorT& x)
426 {
427  x.resize (m.columns (), 1);
428  // the OSE part
429  VectorT y(m.rows (), 1, 0), z(m.rows (), 1);
430  typedef typename VectorT::element_type RR;
431  RR lambda = 0;
432  RR mu = 1;
433  for (octave_idx_type k = 0; k < m.columns (); k++)
434  {
435  octave_quit ();
436  VectorT col (m.column (k));
437  if (k > 0)
438  higham_subp (y, col, 4*k, p, lambda, mu);
439  for (octave_idx_type i = 0; i < k; i++)
440  x(i) *= lambda;
441  x(k) = mu;
442  y = lambda * y + mu * col;
443  }
444 
445  // the PM part
446  x = x / vector_norm (x, p);
447  R q = p/(p-1);
448 
449  R gamma = 0, gamma1;
450  int iter = 0;
451  while (iter < maxiter)
452  {
453  octave_quit ();
454  y = m*x;
455  gamma1 = gamma;
456  gamma = vector_norm (y, p);
457  z = dual_p (y, p, q);
458  z = z.hermitian ();
459  z = z * m;
460 
461  if (iter > 0 && (vector_norm (z, q) <= gamma
462  || (gamma - gamma1) <= tol*gamma))
463  break;
464 
465  z = z.hermitian ();
466  x = dual_p (z, q, p);
467  iter++;
468  }
469 
470  return gamma;
471 }
472 
473 // derive column vector and SVD types
474 
475 static const char *p_less1_gripe = "xnorm: p must be >= 1";
476 
477 // Static constant to control the maximum number of iterations. 100 seems to
478 // be a good value. Eventually, we can provide a means to change this
479 // constant from Octave.
480 static int max_norm_iter = 100;
481 
482 // version with SVD for dense matrices
483 template <typename MatrixT, typename VectorT, typename R>
484 R svd_matrix_norm (const MatrixT& m, R p, VectorT)
485 {
486  R res = 0;
487  if (p == 2)
488  {
491  res = fact.singular_values () (0,0);
492  }
493  else if (p == 1)
494  res = xcolnorms (m, 1).max ();
495  else if (lo_ieee_isinf (p) && p > 1)
496  res = xrownorms (m, 1).max ();
497  else if (p > 1)
498  {
499  VectorT x;
500  const R sqrteps = std::sqrt (std::numeric_limits<R>::epsilon ());
501  res = higham (m, p, sqrteps, max_norm_iter, x);
502  }
503  else
505 
506  return res;
507 }
508 
509 // SVD-free version for sparse matrices
510 template <typename MatrixT, typename VectorT, typename R>
511 R matrix_norm (const MatrixT& m, R p, VectorT)
512 {
513  R res = 0;
514  if (p == 1)
515  res = xcolnorms (m, 1).max ();
516  else if (lo_ieee_isinf (p) && p > 1)
517  res = xrownorms (m, 1).max ();
518  else if (p > 1)
519  {
520  VectorT x;
521  const R sqrteps = std::sqrt (std::numeric_limits<R>::epsilon ());
522  res = higham (m, p, sqrteps, max_norm_iter, x);
523  }
524  else
526 
527  return res;
528 }
529 
530 // and finally, here's what we've promised in the header file
531 
532 #define DEFINE_XNORM_FUNCS(PREFIX, RTYPE) \
533  OCTAVE_API RTYPE xnorm (const PREFIX##ColumnVector& x, RTYPE p) \
534  { \
535  return vector_norm (x, p); \
536  } \
537  OCTAVE_API RTYPE xnorm (const PREFIX##RowVector& x, RTYPE p) \
538  { \
539  return vector_norm (x, p); \
540  } \
541  OCTAVE_API RTYPE xnorm (const PREFIX##Matrix& x, RTYPE p) \
542  { \
543  return svd_matrix_norm (x, p, PREFIX##Matrix ()); \
544  } \
545  OCTAVE_API RTYPE xfrobnorm (const PREFIX##Matrix& x) \
546  { \
547  return vector_norm (x, static_cast<RTYPE> (2)); \
548  }
549 
552 DEFINE_XNORM_FUNCS(Float, float)
554 
555 // this is needed to avoid copying the sparse matrix for xfrobnorm
556 template <typename T, typename R>
557 inline void array_norm_2 (const T *v, octave_idx_type n, R& res)
558 {
560  for (octave_idx_type i = 0; i < n; i++)
561  acc.accum (v[i]);
562 
563  res = acc;
564 }
565 
566 #define DEFINE_XNORM_SPARSE_FUNCS(PREFIX, RTYPE) \
567  OCTAVE_API RTYPE xnorm (const Sparse##PREFIX##Matrix& x, RTYPE p) \
568  { \
569  return matrix_norm (x, p, PREFIX##Matrix ()); \
570  } \
571  OCTAVE_API RTYPE xfrobnorm (const Sparse##PREFIX##Matrix& x) \
572  { \
573  RTYPE res; \
574  array_norm_2 (x.data (), x.nnz (), res); \
575  return res; \
576  }
577 
580 
581 #define DEFINE_COLROW_NORM_FUNCS(PREFIX, RPREFIX, RTYPE) \
582  extern OCTAVE_API RPREFIX##RowVector \
583  xcolnorms (const PREFIX##Matrix& m, RTYPE p) \
584  { \
585  return column_norms (m, p); \
586  } \
587  extern OCTAVE_API RPREFIX##ColumnVector \
588  xrownorms (const PREFIX##Matrix& m, RTYPE p) \
589  { \
590  return row_norms (m, p); \
591  } \
592 
595 DEFINE_COLROW_NORM_FUNCS(Float, Float, float)
597 
599 DEFINE_COLROW_NORM_FUNCS(SparseComplex, , double)
octave_idx_type rows(void) const
Definition: Array.h:404
OCTAVE_API ColumnVector xrownorms(const Matrix &m, double p)
Definition: oct-norm.cc:593
#define DEFINE_XNORM_FUNCS(PREFIX, RTYPE)
Definition: oct-norm.cc:532
T * data(void)
Definition: Sparse.h:486
Template for N-dimensional array classes with like-type math operators.
Definition: MArray.h:32
double max(void) const
Definition: dRowVector.cc:219
#define DEFINE_DISPATCHER(FUNC_NAME, ARG_TYPE, RES_TYPE)
Definition: oct-norm.cc:296
DM_T singular_values(void) const
Definition: svd.h:86
identity matrix If supplied two scalar respectively For allows like xample val
Definition: data.cc:4986
VectorT dual_p(const VectorT &x, R p, R q)
Definition: oct-norm.cc:414
OCTAVE_NORETURN liboctave_error_handler current_liboctave_error_handler
Definition: lo-error.c:38
octave_idx_type columns(void) const
Definition: Sparse.h:260
void accum(U val)
Definition: oct-norm.cc:199
octave_idx_type * cidx(void)
Definition: Sparse.h:508
for large enough k
Definition: lu.cc:617
bool isnan(bool)
Definition: lo-mappers.h:187
static T abs(T x)
Definition: pr-output.cc:1696
void accum(R val)
Definition: oct-norm.cc:134
static double fi[256]
Definition: randmtzig.cc:435
octave_idx_type columns(void) const
Definition: Array.h:413
#define lo_ieee_isinf(x)
Definition: lo-ieee.h:113
OCTAVE_EXPORT octave_value_list return the number of command line arguments passed to Octave If called with the optional argument the function t
Definition: ov-usr-fcn.cc:997
void accum(U val)
Definition: oct-norm.cc:107
T elem_dual_p(T x, R p)
Definition: oct-norm.cc:404
double gamma(double x)
Definition: lo-specfun.cc:1913
static void higham_subp(const ColVectorT &y, const ColVectorT &col, octave_idx_type nsamp, R p, R &lambda, R &mu)
Definition: oct-norm.cc:332
void accum(U val)
Definition: oct-norm.cc:166
static int max_norm_iter
Definition: oct-norm.cc:480
#define DEFINE_COLROW_NORM_FUNCS(PREFIX, RPREFIX, RTYPE)
Definition: oct-norm.cc:581
OCTAVE_API RowVector xcolnorms(const Matrix &m, double p)
Definition: oct-norm.cc:593
double signum(double x)
Definition: lo-mappers.h:244
static R pow2(R x)
Definition: oct-norm.cc:130
octave_int< T > pow(const octave_int< T > &a, const octave_int< T > &b)
octave_idx_type * ridx(void)
Definition: Sparse.h:495
norm_accumulator_p(R pp)
Definition: oct-norm.cc:76
R matrix_norm(const MatrixT &m, R p, VectorT)
Definition: oct-norm.cc:511
unsigned int num
Definition: oct-norm.cc:213
static const char * p_less1_gripe
Definition: oct-norm.cc:475
void accum(std::complex< R > val)
Definition: oct-norm.cc:149
void accum(U val)
Definition: oct-norm.cc:217
T & xelem(octave_idx_type n)
Definition: Array.h:458
N Dimensional Array with copy-on-write semantics.
Definition: Array.h:125
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:227
#define Inf
Definition: Faddeeva.cc:247
#define DEFINE_XNORM_SPARSE_FUNCS(PREFIX, RTYPE)
Definition: oct-norm.cc:566
norm_accumulator_mp(R pp)
Definition: oct-norm.cc:104
OCTAVE_EXPORT octave_value_list or N dimensional array whose elements are all equal to the IEEE symbol NaN(Not a Number). NaN is the result of operations which do not produce a well defined 0 result. Common operations which produce a NaN are arithmetic with infinity ex($\infty - \infty$)
void array_norm_2(const T *v, octave_idx_type n, R &res)
Definition: oct-norm.cc:557
p
Definition: lu.cc:138
void accum(U val)
Definition: oct-norm.cc:79
double max(void) const
Definition: dColVector.cc:259
void row_norms(const MArray< T > &m, MArray< R > &res, ACC acc)
Definition: oct-norm.cc:251
the element is set to zero In other the statement xample y
Definition: data.cc:5264
void vector_norm(const Array< T > &v, R &res, ACC acc)
Definition: oct-norm.cc:227
R svd_matrix_norm(const MatrixT &m, R p, VectorT)
Definition: oct-norm.cc:484
void accum(U val)
Definition: oct-norm.cc:181
for i
Definition: data.cc:5264
void column_norms(const MArray< T > &m, MArray< R > &res, ACC acc)
Definition: oct-norm.cc:237
std::complex< float > FloatComplex
Definition: oct-cmplx.h:32
R higham(const MatrixT &m, R p, R tol, int maxiter, VectorT &x)
Definition: oct-norm.cc:424
std::complex< double > Complex
Definition: oct-cmplx.h:31
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:366
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:87
octave_idx_type rows(void) const
Definition: Sparse.h:258
F77_RET_T const F77_REAL const F77_REAL F77_REAL &F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE &F77_RET_T const F77_DBLE F77_DBLE &F77_RET_T const F77_REAL F77_REAL &F77_RET_T const F77_DBLE * x
charNDArray min(char d, const charNDArray &m)
Definition: chNDArray.cc:204