Vertstat.cc 13.2 KB
Newer Older
Uwe Schulzweida's avatar
Uwe Schulzweida committed
1
2
3
4
/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

Uwe Schulzweida's avatar
Uwe Schulzweida committed
5
  Copyright (C) 2003-2017 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
Uwe Schulzweida's avatar
Uwe Schulzweida committed
6
7
8
9
10
11
12
13
14
15
16
17
  See COPYING file for copying and redistribution conditions.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/

18
19
20
21
22
23
/*
   This module contains the following operators:

      Vertstat   vertmin         Vertical minimum
      Vertstat   vertmax         Vertical maximum
      Vertstat   vertsum         Vertical sum
24
      Vertstat   vertint         Vertical integral
25
26
      Vertstat   vertmean        Vertical mean
      Vertstat   vertavg         Vertical average
Uwe Schulzweida's avatar
Uwe Schulzweida committed
27
      Vertstat   vertvar         Vertical variance
Uwe Schulzweida's avatar
Uwe Schulzweida committed
28
      Vertstat   vertvar1        Vertical variance [Normalize by (n-1)]
29
      Vertstat   vertstd         Vertical standard deviation
Uwe Schulzweida's avatar
Uwe Schulzweida committed
30
      Vertstat   vertstd1        Vertical standard deviation [Normalize by (n-1)]
31
32
33
*/


Ralf Mueller's avatar
Ralf Mueller committed
34
#include <cdi.h>
Uwe Schulzweida's avatar
Uwe Schulzweida committed
35
36
37
38
39
#include "cdo.h"
#include "cdo_int.h"
#include "pstream.h"


Uwe Schulzweida's avatar
Uwe Schulzweida committed
40
41
#define IS_SURFACE_LEVEL(zaxisID)  (zaxisInqType(zaxisID) == ZAXIS_SURFACE && zaxisInqSize(zaxisID) == 1)

42

Uwe Schulzweida's avatar
Uwe Schulzweida committed
43
44
45
46
47
48
49
int getSurfaceID(int vlistID)
{
  int surfID = -1;
  int nzaxis = vlistNzaxis(vlistID);

  for ( int index = 0; index < nzaxis; ++index )
    {
50
      int zaxisID = vlistZaxis(vlistID, index);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
      if ( IS_SURFACE_LEVEL(zaxisID) )
	{
	  surfID = vlistZaxis(vlistID, index);
	  break;
	}
    }

  if ( surfID == -1 ) surfID = zaxisCreate(ZAXIS_SURFACE, 1);

  return surfID;
}

static
void setSurfaceID(int vlistID, int surfID)
{
  int nzaxis = vlistNzaxis(vlistID);

  for ( int index = 0; index < nzaxis; ++index )
    {
70
      int zaxisID = vlistZaxis(vlistID, index);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
71
72
73
74
75
      if ( zaxisID != surfID || !IS_SURFACE_LEVEL(zaxisID) )
	vlistChangeZaxisIndex(vlistID, index, surfID);
    }
}

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

void genLayerBounds(int nlev, double *levels, double *lbounds, double *ubounds)
{
  if ( nlev == 1 )
    {
      lbounds[0] = 0.;
      ubounds[0] = 1.;
    }
  else
    {
      lbounds[0]      = levels[0];
      ubounds[nlev-1] = levels[nlev-1];
      for ( int i = 0; i < nlev-1; ++i )
        {
          double bound = 0.5*(levels[i] + levels[i+1]);
          lbounds[i+1] = bound;
          ubounds[i]   = bound;
        }
    }
}

97

98
int getLayerThickness(bool genbounds, int index, int zaxisID, int nlev, double *thickness, double *weights)
Uwe Schulzweida's avatar
Uwe Schulzweida committed
99
{
100
  int status = 0;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
101
  int i;
102
103
104
  double *levels  = (double *) Malloc(nlev*sizeof(double));
  double *lbounds = (double *) Malloc(nlev*sizeof(double));
  double *ubounds = (double *) Malloc(nlev*sizeof(double));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
105

106
  cdoZaxisInqLevels(zaxisID, levels);
107
  if ( genbounds )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
108
    {
109
      status = 2;
110
      genLayerBounds(nlev, levels, lbounds, ubounds);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
111
    }
112
113
114
115
116
117
  else if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
    {
      status = 1;
      zaxisInqLbounds(zaxisID, lbounds);
      zaxisInqUbounds(zaxisID, ubounds);
    }
118
119
120
121
122
123
124
125
126
  else
    {
      for ( i = 0; i < nlev; ++i )
	{
	  lbounds[i] = 0.;
	  ubounds[i] = 1.;
	}
    }
  
127
  for ( i = 0; i < nlev; ++i ) thickness[i] = fabs(ubounds[i]-lbounds[i]);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
128

129
130
131
  double lsum = 0;
  for ( i = 0; i < nlev; ++i ) lsum += thickness[i];

132
133
  for ( i = 0; i < nlev; ++i ) weights[i] = thickness[i];
  
134
135
  for ( i = 0; i < nlev; ++i ) weights[i] /= (lsum/nlev);

Uwe Schulzweida's avatar
Uwe Schulzweida committed
136
137
  double wsum = 0;
  for ( i = 0; i < nlev; ++i ) wsum += weights[i];
138

Uwe Schulzweida's avatar
Uwe Schulzweida committed
139
140
  if ( cdoVerbose )
    {
141
      cdoPrint("zaxisID=%d  nlev=%d  layersum=%g  weightsum=%g", index, nlev, lsum, wsum);
142
      printf("         level     bounds   thickness  weight\n");
143
      for ( i = 0; i < nlev; ++i )
144
	printf("   %3d  %6g  %6g/%-6g  %6g  %6g\n", i+1, levels[i], lbounds[i], ubounds[i], thickness[i], weights[i]);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
145
146
    }

147
148
149
  Free(levels);
  Free(lbounds);
  Free(ubounds);
150
151

  return status;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
152
}
Uwe Schulzweida's avatar
Uwe Schulzweida committed
153

154

Uwe Schulzweida's avatar
Uwe Schulzweida committed
155
156
void *Vertstat(void *argument)
{
157
  int nrecs;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
158
  int gridID;
159
160
  int varID, levelID;
  int nmiss;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
161
162
  typedef struct {
    int zaxisID;
163
    int status;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
164
    int numlevel;
165
    double *thickness;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
166
167
168
    double *weights;
  }
  vert_t;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
169
170
171

  cdoInitialize(argument);

172
173
174
175
                 cdoOperatorAdd("vertmin",  func_min,  0, NULL);
                 cdoOperatorAdd("vertmax",  func_max,  0, NULL);
                 cdoOperatorAdd("vertsum",  func_sum,  0, NULL);
  int VERTINT  = cdoOperatorAdd("vertint",  func_sum,  1, NULL);
176
                 cdoOperatorAdd("vertmean", func_mean, 1, NULL);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
177
                 cdoOperatorAdd("vertavg",  func_avg,  1, NULL);
178
179
180
181
                 cdoOperatorAdd("vertvar",  func_var,  1, NULL);
                 cdoOperatorAdd("vertvar1", func_var1, 1, NULL);
                 cdoOperatorAdd("vertstd",  func_std,  1, NULL);
                 cdoOperatorAdd("vertstd1", func_std1, 1, NULL);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
182

183
184
185
  int operatorID   = cdoOperatorID();
  int operfunc     = cdoOperatorF1(operatorID);
  bool needWeights = cdoOperatorF2(operatorID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
186

187
  bool lmean   = operfunc == func_mean || operfunc == func_avg;
188
189
190
  bool lstd    = operfunc == func_std || operfunc == func_std1;
  bool lvarstd = operfunc == func_std || operfunc == func_var || operfunc == func_std1 || operfunc == func_var1;
  int divisor  = operfunc == func_std1 || operfunc == func_var1;
191

192
  //int applyWeights = lmean;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
193

194
  int streamID1 = streamOpenRead(cdoStreamName(0));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
195

196
  int vlistID1 = streamInqVlist(streamID1);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
197
198

  vlistClearFlag(vlistID1);
199
  int nvars = vlistNvars(vlistID1);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
200
201
202
  for ( varID = 0; varID < nvars; varID++ )
    vlistDefFlag(vlistID1, varID, 0, TRUE);

203
  int vlistID2 = vlistCreate();
Uwe Schulzweida's avatar
Uwe Schulzweida committed
204
205
  vlistCopyFlag(vlistID2, vlistID1);

206
207
  int taxisID1 = vlistInqTaxis(vlistID1);
  int taxisID2 = taxisDuplicate(taxisID1);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
208
209
  vlistDefTaxis(vlistID2, taxisID2);

Uwe Schulzweida's avatar
Uwe Schulzweida committed
210
211
212
213
214
215
216
217
  int surfID = getSurfaceID(vlistID1);
  setSurfaceID(vlistID2, surfID);

  int nzaxis = vlistNzaxis(vlistID1);
  int nlev, zaxisID;
  vert_t vert[nzaxis];
  if ( needWeights )
    {
218
      bool genbounds = false;
219
      unsigned npar = operatorArgc();
220
221
222
223
224
      if ( npar > 0 )
	{
	  char **parnames = operatorArgv();

	  if ( cdoVerbose )
225
	    for ( unsigned i = 0; i < npar; i++ )
226
227
	      cdoPrint("key %d = %s", i+1, parnames[i]);

228
	  if ( strcmp(parnames[0], "genbounds") == 0 ) genbounds = true;
229
	  else cdoAbort("Parameter >%s< unsupported! Supported parameter are: genbounds", parnames[0]);
230
231
	}
      
Uwe Schulzweida's avatar
Uwe Schulzweida committed
232
233
234
235
236
      for ( int index = 0; index < nzaxis; ++index )
	{
	  zaxisID = vlistZaxis(vlistID1, index);
	  nlev = zaxisInqSize(zaxisID);
	  vert[index].numlevel = 0;
237
	  vert[index].status   = 0;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
238
	  vert[index].zaxisID  = zaxisID;
239
	  // if ( nlev > 1 )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
240
241
	    {
	      vert[index].numlevel = nlev;
242
243
	      vert[index].thickness = (double *) Malloc(nlev*sizeof(double));
	      vert[index].weights = (double *) Malloc(nlev*sizeof(double));
244
	      vert[index].status = getLayerThickness(genbounds, index, zaxisID, nlev, vert[index].thickness, vert[index].weights); 
Uwe Schulzweida's avatar
Uwe Schulzweida committed
245
246
247
	    }
	}
    }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
248

249
  int streamID2 = streamOpenWrite(cdoStreamName(1), cdoFiletype());
Uwe Schulzweida's avatar
Uwe Schulzweida committed
250
251
252

  streamDefVlist(streamID2, vlistID2);

253
  int gridsize = vlistGridsizeMax(vlistID1);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
254

255
  field_type field;
256
  field_init(&field);
257
  field.ptr = (double*) Malloc(gridsize*sizeof(double));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
258

259
260
261
  field_type *vars1 = (field_type*) Malloc(nvars*sizeof(field_type));
  field_type *samp1 = (field_type*) Malloc(nvars*sizeof(field_type));
  field_type *vars2 = NULL;
262
  if ( lvarstd )
263
    vars2 = (field_type*) Malloc(nvars*sizeof(field_type));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
264
265
266
267
268

  for ( varID = 0; varID < nvars; varID++ )
    {
      gridID   = vlistInqVarGrid(vlistID1, varID);
      gridsize = gridInqSize(gridID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
269
      zaxisID  = vlistInqVarZaxis(vlistID1, varID);
270
      double missval = vlistInqVarMissval(vlistID1, varID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
271

272
273
      field_init(&vars1[varID]);
      field_init(&samp1[varID]);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
274
      vars1[varID].grid    = gridID;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
275
      vars1[varID].zaxis   = zaxisID;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
276
277
278
      vars1[varID].nsamp   = 0;
      vars1[varID].nmiss   = 0;
      vars1[varID].missval = missval;
279
      vars1[varID].ptr     = (double*) Malloc(gridsize*sizeof(double));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
280
281
282
283
      samp1[varID].grid    = gridID;
      samp1[varID].nmiss   = 0;
      samp1[varID].missval = missval;
      samp1[varID].ptr     = NULL;
284
      if ( lvarstd )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
285
	{
286
	  field_init(&vars2[varID]);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
287
288
289
	  vars2[varID].grid    = gridID;
	  vars2[varID].nmiss   = 0;
	  vars2[varID].missval = missval;
290
	  vars2[varID].ptr     = (double*) Malloc(gridsize*sizeof(double));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
291
292
293
	}
    }

294
  int tsID = 0;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
295
296
297
298
299
  while ( (nrecs = streamInqTimestep(streamID1, tsID)) )
    {
      taxisCopyTimestep(taxisID2, taxisID1);
      streamDefTimestep(streamID2, tsID);

300
      for ( int recID = 0; recID < nrecs; recID++ )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
301
302
	{
	  streamInqRecord(streamID1, &varID, &levelID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
303

Uwe Schulzweida's avatar
Uwe Schulzweida committed
304
          vars1[varID].nsamp++;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
305
	  gridsize = gridInqSize(vars1[varID].grid);
306
307
	  zaxisID  = vars1[varID].zaxis;
	  nlev = zaxisInqSize(zaxisID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
308

309
310
311
	  double layer_weight = 1.0;
	  double layer_thickness = 1.0;
	  if ( needWeights )
312
313
314
	    {
	      for ( int index = 0; index < nzaxis; ++index )
		if ( vert[index].zaxisID == zaxisID )
315
		  {		    
316
		    if ( vert[index].status == 0 && tsID == 0 && levelID == 0 && nlev > 1 )
317
318
319
		      {
			char varname[CDI_MAX_NAME];
			vlistInqVarName(vlistID1, varID, varname);
320
			cdoWarning("Layer bounds not available, using constant vertical weights for variable %s!", varname);
321
		      }
322
323
		    else
		      {
324
325
			layer_weight    = vert[index].weights[levelID];
			layer_thickness = vert[index].thickness[levelID];
326
		      }
327
328
329
330

		    break;
		  }
	    }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
331

Uwe Schulzweida's avatar
Uwe Schulzweida committed
332
333
334
	  if ( levelID == 0 )
	    {
	      streamReadRecord(streamID1, vars1[varID].ptr, &nmiss);
335
	      vars1[varID].nmiss = (size_t)nmiss;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
336

337
	      if ( operatorID == VERTINT && IS_NOT_EQUAL(layer_thickness, 1.0) ) farcmul(&vars1[varID], layer_thickness);
338
	      if ( lmean && IS_NOT_EQUAL(layer_weight, 1.0) ) farcmul(&vars1[varID], layer_weight);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
339

340
	      if ( lvarstd )
341
342
343
344
345
346
347
348
349
350
351
                {
                  if ( IS_NOT_EQUAL(layer_weight, 1.0) )
                    {
                      farmoqw(&vars2[varID], vars1[varID], layer_weight);
                      farcmul(&vars1[varID], layer_weight);
                    }
                  else
                    {
                      farmoq(&vars2[varID], vars1[varID]);
                    }
                }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
352

353
	      if ( nmiss > 0 || samp1[varID].ptr || needWeights )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
354
355
		{
		  if ( samp1[varID].ptr == NULL )
356
		    samp1[varID].ptr = (double *) Malloc(gridsize*sizeof(double));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
357

358
		  for ( int i = 0; i < gridsize; i++ )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
359
		    if ( DBL_IS_EQUAL(vars1[varID].ptr[i], vars1[varID].missval) )
360
		      samp1[varID].ptr[i] = 0.;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
361
		    else
362
		      samp1[varID].ptr[i] = layer_weight;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
363
364
365
366
		}
	    }
	  else
	    {
367
368
	      streamReadRecord(streamID1, field.ptr, &nmiss);
              field.nmiss   = (size_t)nmiss;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
369
370
371
	      field.grid    = vars1[varID].grid;
	      field.missval = vars1[varID].missval;

372
	      if ( operatorID == VERTINT && IS_NOT_EQUAL(layer_thickness, 1.0) ) farcmul(&field, layer_thickness);
373
	      if ( lmean && IS_NOT_EQUAL(layer_weight, 1.0) ) farcmul(&field, layer_weight);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
374

Uwe Schulzweida's avatar
Uwe Schulzweida committed
375
376
377
378
	      if ( field.nmiss > 0 || samp1[varID].ptr )
		{
		  if ( samp1[varID].ptr == NULL )
		    {
379
		      samp1[varID].ptr = (double*) Malloc(gridsize*sizeof(double));
380
		      for ( int i = 0; i < gridsize; i++ )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
381
382
383
			samp1[varID].ptr[i] = vars1[varID].nsamp;
		    }

384
		  for ( int i = 0; i < gridsize; i++ )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
385
		    if ( !DBL_IS_EQUAL(field.ptr[i], vars1[varID].missval) )
386
		      samp1[varID].ptr[i] += layer_weight;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
387
388
		}

389
	      if ( lvarstd )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
390
		{
391
392
393
394
395
396
397
398
399
400
                  if ( IS_NOT_EQUAL(layer_weight, 1.0) )
                    {
                      farsumqw(&vars2[varID], field, layer_weight);
                      farsumw(&vars1[varID], field, layer_weight);
                    }
                  else
                    {
                      farsumq(&vars2[varID], field);
                      farsum(&vars1[varID], field);
                    }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
401
402
403
404
405
406
407
408
409
410
		}
	      else
		{
		  farfun(&vars1[varID], field, operfunc);
		}
	    }
	}

      for ( varID = 0; varID < nvars; varID++ )
	{
411
412
	  if ( vars1[varID].nsamp )
	    {
413
	      if ( lmean )
414
415
		{
		  if ( samp1[varID].ptr == NULL )
416
		    farcdiv(&vars1[varID], (double)vars1[varID].nsamp);
417
418
419
		  else
		    fardiv(&vars1[varID], samp1[varID]);
		}
420
	      else if ( lvarstd )
421
422
423
		{
		  if ( samp1[varID].ptr == NULL )
		    {
424
		      if ( lstd )
425
			farcstd(&vars1[varID], vars2[varID], vars1[varID].nsamp, divisor);
426
		      else
427
			farcvar(&vars1[varID], vars2[varID], vars1[varID].nsamp, divisor);
428
429
430
		    }
		  else
		    {
431
		      if ( lstd )
432
			farstd(&vars1[varID], vars2[varID], samp1[varID], divisor);
433
		      else
434
			farvar(&vars1[varID], vars2[varID], samp1[varID], divisor);
435
436
437
438
		    }
		}

	      streamDefRecord(streamID2, varID, 0);
439
	      streamWriteRecord(streamID2, vars1[varID].ptr, (int)vars1[varID].nmiss);
440
441
	      vars1[varID].nsamp = 0;
	    }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
442
443
444
445
446
447
448
	}

      tsID++;
    }

  for ( varID = 0; varID < nvars; varID++ )
    {
449
450
451
      Free(vars1[varID].ptr);
      if ( samp1[varID].ptr ) Free(samp1[varID].ptr);
      if ( lvarstd ) Free(vars2[varID].ptr);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
452
453
    }

454
455
456
  Free(vars1);
  Free(samp1);
  if ( lvarstd ) Free(vars2);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
457

458
  if ( field.ptr ) Free(field.ptr);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
459

Uwe Schulzweida's avatar
Uwe Schulzweida committed
460
  if ( needWeights )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
461
    for ( int index = 0; index < nzaxis; ++index )
462
      if ( vert[index].numlevel > 1 )  Free(vert[index].weights);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
463

Uwe Schulzweida's avatar
Uwe Schulzweida committed
464
465
466
  streamClose(streamID2);
  streamClose(streamID1);

467
468
  vlistDestroy(vlistID2);

Uwe Schulzweida's avatar
Uwe Schulzweida committed
469
470
  cdoFinish();

Uwe Schulzweida's avatar
Uwe Schulzweida committed
471
  return 0;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
472
}