summaryrefslogtreecommitdiff
path: root/sim/src/jigs.c
blob: 88a309df2bf6b59c81793bebac7a112728f4c0df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
// Copyright 2005-2006 Nanorex, Inc.  See LICENSE file for details. 
//#define WWDEBUG
#include "simulator.h"

static char const rcsid[] = "$Id$";

/** kT @ 300K is 4.14 zJ -- RMS V of carbon is 1117 m/s
    or 645 m/s each dimension, or 0.645 pm/fs  */

static double gavss(double v) {
    double v0,v1, rSquared;
    do {
	v0=(float)rand()/(float)(RAND_MAX/2) - 1.0;
	v1=(float)rand()/(float)(RAND_MAX/2) - 1.0;
	rSquared = v0*v0 + v1*v1;
    } while (rSquared>=1.0 || rSquared==0.0);
    return v*v0*sqrt(-2.0*log(rSquared)/rSquared);
}

struct xyz gxyz(double v) {
    struct xyz g;
    g.x=gavss(v);
    g.y=gavss(v);
    g.z=gavss(v);
    return g;
}

void
jigGround(struct jig *jig, double deltaTframe, struct xyz *position, struct xyz *new_position, struct xyz *force)
{
    struct xyz foo, bar;
    struct xyz q1;
    int k;
    struct xyz rx;

    vsetc(foo,0.0);
    vsetc(q1,0.0);
    for (k=0; k<jig->num_atoms; k++) { // find center
        vadd(foo,position[jig->atoms[k]->index]);
    }
    vmulc(foo,1.0 / jig->num_atoms);

    for (k=0; k<jig->num_atoms; k++) {
        vsub2(rx,position[jig->atoms[k]->index], foo);
        v2x(bar,rx,force[jig->atoms[k]->index]); // bar = rx cross force[]
        vadd(q1,bar);
    }
    vmulc(q1,deltaTframe);
    vadd(jig->xdata, q1);
    jig->data++;

    for (k=0; k<jig->num_atoms; k++) {
        new_position[jig->atoms[k]->index] = position[jig->atoms[k]->index];
    }
}

/*
 * Springs connect atoms to a flywheel. We drive the flywheel and it
 * pulls the atoms along. The units of spring stiffness are piconewtons
 * per picometer, or equivalently newtons per meter.
 *
 * 10 newtons/meter is too stiff, we get oscillations that grow out of
 * control. 1 N/m and 0.1 N/m give oscillations but they don't go crazy.
 */
#define SPRING_STIFFNESS  10.0

/*
 * We want a damping coefficient that is dimensionless and ranges from
 * zero to one, where zero is no damping at all, and one is complete
 * damping, no sinusoidal component at all.
 *
 * The magic equation here is Mx'' = -Kx - Dx', where M is atom mass,
 * x is atom displacement in the direction of the spring, K is spring
 * stiffness. D is a constant which multiplies by a velocity to give a
 * force, and it acts like friction. Plug in Laplace operator "s" as a
 * derivative, and we have s**2 + (D/M)s  + (K/M) = 0. The solutions to
 * this equation describe the two mechanical resonances of the system,
 * and the negative-ness of the real parts tell us how quickly the
 * oscillations in omega die away.
 *
 * Let z be our dimensionless number between zero and one, and let D =
 * 2z * sqrt(KM). Then the resonances occur at
 *
 *     s = sqrt(K/M) * (-z +/- j * sqrt(1-z**2))
 *
 * where "j" is sqrt(-1), giving the imaginary part of s. The real
 * part is negative, indicating that it's stable. A positive real part
 * would mean the oscillations were going to grow with time, a bad
 * thing.
 */

void
jigMotor(struct jig *jig, double deltaTframe, struct xyz *position, struct xyz *new_position, struct xyz *force)
{
    int k;
    int a1;
    struct xyz tmp;
    struct xyz f;
    struct xyz r;
    double omega, domega_dt;
    double motorq, dragTorque = 0.0;
    double theta, cos_theta, sin_theta;
    double m;
    const double minspeed = 1.0;  // radians per second, very slow
    const double maxm = 1.0e-4;  // max value for multiplier, make it stable
    struct xyz anchor;

#ifdef WWDEBUG
    /*
     * Bug 1529, where two rotary motors fight. Even though one has
     * zero torque, the torque isn't really zero because it's got that
     * big flywheel to get up to speed. The weak links are the chemical
     * bonds, which go nuts.
     */
    int explain_stuff = 0;
    {
	static long count = 0;
	// odd modulo, so we switch motors
	if ((count % 101) == 0) {
	    MARK();
	    explain_stuff = 1;
	}
	count++;
    }
#endif

    // omega is current motor speed in radians per second.
    // jig->j.rmotor.speed is top (zero torque) speed in radians per second.
    // jig->j.rmotor.stall is zero speed torque in pN-pm or yNm (yocto Nm, 1e-24 Kg m^2 sec^-2)
    omega = jig->j.rmotor.omega;
    // Bosch model
    if (fabs(jig->j.rmotor.speed) < minspeed) {
	if (jig->j.rmotor.speed >= 0) {
	    m = jig->j.rmotor.stall / minspeed;
	} else {
	    m = -jig->j.rmotor.stall / minspeed;
	}
    } else {
	m = jig->j.rmotor.stall / jig->j.rmotor.speed;
    }
    // clip m to maintain stability
    if (m > maxm) m = maxm;
    else if (m < -maxm) m = -maxm;
    // m is yocto Kg m^2 sec^-1 radian^-1: it converts radians/second to pN-pm
    // motorq is yNm
    motorq = m * (jig->j.rmotor.speed - omega);
    // don't let the torque get too big
    if (motorq > 2.0 * jig->j.rmotor.stall) {
	motorq = 2.0 * jig->j.rmotor.stall;
    } else if (motorq < -2.0 * jig->j.rmotor.stall) {
	motorq = -2.0 * jig->j.rmotor.stall;
    }

    cos_theta = cos(jig->j.rmotor.theta);
    sin_theta = sin(jig->j.rmotor.theta);

    /* nudge atoms toward their new places */
    for (k = 0; k < jig->num_atoms; k++) {
	struct xyz rprev;
	a1 = jig->atoms[k]->index;
	// get the position of this atom's anchor
	anchor = jig->j.rmotor.center;
	vadd(anchor, jig->j.rmotor.u[k]);
	vmul2c(tmp, jig->j.rmotor.v[k], cos_theta);
	vadd(anchor, tmp);
	vmul2c(tmp, jig->j.rmotor.w[k], sin_theta);
	vadd(anchor, tmp);
        if (_last_iteration && DEBUG(D_DYNAMICS_SIMPLE_MOVIE)) { // -D15
            writeSimplePositionMarker(&anchor, 5.0, 1.0, 1.0, 1.0);
            writeSimplePositionMarker(&jig->j.rmotor.center, 5.0, 1.0, 1.0, 1.0);
        }
	// compute a force pushing on the atom, spring term plus damper term
	r = position[a1];
	vsub(r, anchor);
	rprev = r;
        // r in pm, SPRING_STIFFNESS in N/m, f in pN
	vmul2c(f, r, -SPRING_STIFFNESS);
	if (jig->j.rmotor.damping_enabled) {

	    // this could be optimized a bit more but the intent would be less clear
	    // frictionOverDt = 2 * jig->j.rmotor.dampingCoefficient *
	    //     sqrt(SPRING_STIFFNESS / jig->atoms[k]->inverseMass);
	    // vmul2c(tmp, r, -frictionOverDt);

	    // friction is force divided by velocity
	    double friction = 2 * jig->j.rmotor.dampingCoefficient *
		sqrt(SPRING_STIFFNESS * 1.e-27 * jig->atoms[k]->mass);
	    vsub(r, jig->j.rmotor.rPrevious[k]);
	    // we need a factor of Dt because of Verlet integration
	    vmul2c(tmp, r, -friction / Dt);
	    vadd(f, tmp);
	    jig->j.rmotor.rPrevious[k] = rprev;
	}

	// nudge the new positions accordingly
	vadd2scale(new_position[a1], f, jig->atoms[k]->inverseMass);

	// compute the drag torque pulling back on the motor
	r = vdif(position[a1], jig->j.rmotor.center);
	tmp = vx(r, f);
	dragTorque += vdot(tmp, jig->j.rmotor.axis);
    }

    domega_dt = (motorq - dragTorque) / jig->j.rmotor.momentOfInertia;
    theta = jig->j.rmotor.theta + omega * Dt;
    jig->j.rmotor.omega = omega = jig->j.rmotor.omega + domega_dt * Dt;

    /* update the motor's position */
    theta = fmod(theta, 2.0 * Pi);
    jig->j.rmotor.theta = theta;
    // convert rad/sec to GHz
    jig->data = jig->j.rmotor.omega / (2.0e9 * Pi);
    // convert from pN-pm to nN-nm
    jig->data2 = motorq / ((1e-9/Dx) * (1e-9/Dx));
}

double
jigMinimizePotentialRotaryMotor(struct part *p, struct jig *jig,
                                struct xyz *positions,
                                double *pTheta)
{
    int k;
    int a1;
    // potential is in aJ (1e-18 J, 1e-18 N m)
    // here, aJ radians
    // torque is aN m (nN-nm)
    double potential = -jig->j.rmotor.minimizeTorque * *pTheta;
    double cos_theta = cos(*pTheta);
    double sin_theta = sin(*pTheta);
    struct xyz tmp;
    struct xyz r;
    struct xyz anchor;

    for (k = 0; k < jig->num_atoms; k++) {
	a1 = jig->atoms[k]->index;
	// get the position of this atom's anchor
	anchor = jig->j.rmotor.center;
	vadd(anchor, jig->j.rmotor.u[k]);
	vmul2c(tmp, jig->j.rmotor.v[k], cos_theta);
	vadd(anchor, tmp);
	vmul2c(tmp, jig->j.rmotor.w[k], sin_theta);
	vadd(anchor, tmp);
        if (DEBUG(D_MINIMIZE_POTENTIAL_MOVIE)) { // -D4
            writeSimplePositionMarker(&anchor, 5.0, 1.0, 1.0, 1.0);
            writeSimplePositionMarker(&jig->j.rmotor.center, 5.0, 1.0, 1.0, 1.0);
        }
	// compute potential of the  spring term
	r = positions[a1];
	vsub(r, anchor);
        // r in pm
        // SPRING_STIFFNESS is in N/m
        // potential in N/m * pm * pm * 1e-6 am/ym or aJ
        potential += 0.5 * SPRING_STIFFNESS * vdot(r, r) * 1e-6; 
    }
    
    return potential;
}

// force is in pN (1e-12 J/m)
// gradient is in pJ/radian
void
jigMinimizeGradientRotaryMotor(struct part *p, struct jig *jig,
                               struct xyz *positions,
                               struct xyz *force,
                               double *pTheta,
                               double *pGradient)
{
    int k;
    int a1;
    double gradient = jig->j.rmotor.minimizeTorque; // aN m
    double cos_theta = cos(*pTheta);
    double sin_theta = sin(*pTheta);
    struct xyz tmp;
    struct xyz r;
    struct xyz f;
    struct xyz anchor;

    for (k = 0; k < jig->num_atoms; k++) {
	a1 = jig->atoms[k]->index;
	// get the position of this atom's anchor
	anchor = jig->j.rmotor.center;
	vadd(anchor, jig->j.rmotor.u[k]);
	vmul2c(tmp, jig->j.rmotor.v[k], cos_theta);
	vadd(anchor, tmp);
	vmul2c(tmp, jig->j.rmotor.w[k], sin_theta);
	vadd(anchor, tmp);
        if (DEBUG(D_MINIMIZE_GRADIENT_MOVIE)) { // -D4
            writeSimplePositionMarker(&anchor, 5.0, 1.0, 1.0, 1.0);
            writeSimplePositionMarker(&jig->j.rmotor.center, 5.0, 1.0, 1.0, 1.0);
        }

        // compute a force pushing on the atom due to a spring to the anchor position
	r = positions[a1];
	vsub(r, anchor);
        // r in pm, SPRING_STIFFNESS in N/m, f in pN
	vmul2c(f, r, -SPRING_STIFFNESS);
        vadd(force[a1], f);

	// compute the drag torque pulling back on the motor
	r = vdif(positions[a1], jig->j.rmotor.center);
        // r in pm, f in pN, tmp in yJ, multiply by 1e-6 to get aJ
	tmp = vx(r, f);
	gradient -= vdot(tmp, jig->j.rmotor.axis) * 1e-6; // axis is unit vector
    }

    *pGradient = gradient;
}

void
jigLinearMotor(struct jig *jig, struct xyz *position, struct xyz *new_position, struct xyz *force, double deltaTframe)
{
    int i;
    int a1;
    struct xyz r;
    struct xyz f;
    double ff, x;

    // calculate the average position of all atoms in the motor (r)
    r = vcon(0.0);
    for (i=0;i<jig->num_atoms;i++) {
        /* for each atom connected to the "shaft" */
        r=vsum(r,position[jig->atoms[i]->index]);
    }
    r=vprodc(r, 1.0/jig->num_atoms);

    // x is length of projection of r onto axis (axis is unit vector)
    x=vdot(r,jig->j.lmotor.axis);
    jig->data = x - jig->j.lmotor.motorPosition;

    // f is the amount of force to apply to each atom.  Always a
    // vector along the motor axis.
    if (jig->j.lmotor.stiffness == 0.0) {
        vset(f, jig->j.lmotor.constantForce);
    } else {
	// zeroPosition is projection distance of r onto axis for 0 force
	ff = jig->j.lmotor.stiffness * (jig->j.lmotor.zeroPosition - x) / jig->num_atoms;
	f = vprodc(jig->j.lmotor.axis, ff);
    }
    // Calculate the resulting force on each atom, and project it onto
    // the motor axis.  This dissapates lateral force from the system
    // without translating it anywhere else, or reporting it out.
    // XXX report resulting force on linear bearing out to higher level
    for (i=0;i<jig->num_atoms;i++) {
        a1 = jig->atoms[i]->index;
        // constrain new_position to be along motor axis from position
        ff = vdot(vdif(new_position[a1], position[a1]), jig->j.lmotor.axis);
        vadd2(new_position[a1], position[a1], vprodc(jig->j.lmotor.axis, ff));

        // add f to force, and remove everything except the axial component
        ff = vdot(vsum(force[a1], f), jig->j.lmotor.axis) ;
        vmul2c(force[a1], jig->j.lmotor.axis, ff);
    }
}

// note linear motor has zero degrees of freedom, so pDistance is not valid.
double
jigMinimizePotentialLinearMotor(struct part *p, struct jig *jig,
                                struct xyz *position,
                                double *pDistance)
{
    int i;
    struct xyz r;
    double x;
    double potential;

    // calculate the average position of all atoms in the motor (r)
    r = vcon(0.0);
    for (i=0;i<jig->num_atoms;i++) {
        /* for each atom connected to the "shaft" */
        r=vsum(r,position[jig->atoms[i]->index]);
    }
    r=vprodc(r, 1.0/jig->num_atoms);

    // x is length of projection of r onto axis (axis is unit vector)
    x=vdot(r,jig->j.lmotor.axis);

    if (jig->j.lmotor.stiffness == 0.0) {
	// motorPosition is projection distance of r onto axis for 0 displacement
        x -= jig->j.lmotor.motorPosition;
        // x in pm, jig->j.lmotor.force in pN
        // x * force in yJ
        potential = x * jig->j.lmotor.force * -1e-6; // in aJ
    } else {
	// zeroPosition is projection distance of r onto axis for 0 force
        x -= jig->j.lmotor.zeroPosition;
        // x in pm, stiffness in N/m
        // stiffness * x * x / 2 in yJ
	potential = jig->j.lmotor.stiffness * x * x * 0.5 * 1e-6; // in aJ
    }
    return potential;
}

void
jigMinimizeGradientLinearMotor(struct part *p, struct jig *jig,
                               struct xyz *position,
                               struct xyz *force,
                               double *pDistance,
                               double *pGradient)
{
    int i;
    int a1;
    struct xyz r;
    struct xyz f;
    double ff, x;

    // calculate the average position of all atoms in the motor (r)
    r = vcon(0.0);
    for (i=0;i<jig->num_atoms;i++) {
        /* for each atom connected to the "shaft" */
        r=vsum(r,position[jig->atoms[i]->index]);
    }
    r=vprodc(r, 1.0/jig->num_atoms);

    // x is length of projection of r onto axis (axis is unit vector)
    x=vdot(r,jig->j.lmotor.axis);

    // f is the amount of force to apply to each atom.  Always a
    // vector along the motor axis.
    if (jig->j.lmotor.stiffness == 0.0) {
        vset(f, jig->j.lmotor.constantForce);
    } else {
	// zeroPosition is projection distance of r onto axis for 0 force
	ff = jig->j.lmotor.stiffness * (jig->j.lmotor.zeroPosition - x) / jig->num_atoms;
	f = vprodc(jig->j.lmotor.axis, ff);
    }
    // Calculate the resulting force on each atom, and project it onto
    // the motor axis.
    for (i=0;i<jig->num_atoms;i++) {
        a1 = jig->atoms[i]->index;
        // position constraints have already been applied, and
        // besides, with no non-axial forces we should never get off
        // axis anyway...

        // add f to force, and remove everything except the axial component
        ff = vdot(vsum(force[a1], f), jig->j.lmotor.axis) ;
        vmul2c(force[a1], jig->j.lmotor.axis, ff);
    }
}

void
jigThermometer(struct jig *jig, double deltaTframe, struct xyz *position, struct xyz *new_position)
{
    double z;
    double ff;
    int a1;
    int k;
    int dof; // degrees of freedom
    struct xyz f;

    dof = 3 * jig->num_atoms;

    // average(m * v * v / 2) == 3 * k * T / 2

    // This divides out both the number of iterations (to average one
    // frame), and the number of degrees of freedom.  DOF = 3N-3 if
    // translation has been cancelled, and 3N-6 if rotation has been
    // cancelled as well.
    z = deltaTframe / ((double)dof);
    ff=0.0;
    for (k=0; k<jig->num_atoms; k++) {
	a1 = jig->atoms[k]->index;
        // f is pm/fs (actually Dt/Dx)
	f = vdif(position[a1],new_position[a1]);
        // mass in yg (1e-24 g)
        // ff in yg Dt*Dt/Dx*Dx
	ff += vdot(f, f) * jig->atoms[k]->mass;
    }
    // Boltz is in J/K
    // ff in 1e3 g m m K / s s J   or K
    ff *= Dmass * Dx * Dx / (Dt * Dt * Boltz);
    jig->data += ff*z;
}

// Langevin thermostat
void
jigThermostat(struct jig *jig, double deltaTframe, struct xyz *position, struct xyz *new_position)
{
    double z;
    double ke;
    int a1;
    int k;
    double therm;
    struct xyz v1;
    struct xyz v2;
    double ff;
    double mass;

    z = deltaTframe / (3 * jig->num_atoms);
    ke=0.0;

    for (k=0; k<jig->num_atoms; k++) {
	a1 = jig->atoms[k]->index;
	mass = jig->atoms[k]->mass;
	therm = sqrt((Boltz*jig->j.thermostat.temperature)/
		     (mass * 1e-27))*Dt/Dx;
        v1 = vdif(new_position[a1],position[a1]);
        ff = vdot(v1, v1) * mass;

        vmulc(v1, 1.0 - ThermostatGamma);
        v2= gxyz(ThermostatG1 * therm);
        vadd(v1, v2);
        vadd2(new_position[a1],position[a1],v1);

        // add up the energy
        ke += vdot(v1, v1) * mass - ff;

    }
    ke *= 0.5 * Dx*Dx/(Dt*Dt) * 1e-27 * 1e18;
    jig->data += ke;
}

double
angleBetween(struct xyz xyz1, struct xyz xyz2)
{
    double Lsq1, Lsq2, dprod;
    Lsq1 = vdot(xyz1, xyz1);
    if (Lsq1 < 1.0e-10)
	return 0.0;
    Lsq2 = vdot(xyz2, xyz2);
    if (Lsq2 < 1.0e-10)
	return 0.0;
    dprod = vdot(xyz1, xyz2) / sqrt(Lsq1 * Lsq2);
    if (dprod >= 1.0)
	return 0.0;
    if (dprod <= -1.0)
	return 180.0;
    return (180.0 / Pi) * acos(dprod);
}


void
jigDihedral(struct jig *jig, struct xyz *new_position)
{
    struct xyz wx;
    struct xyz xy;
    struct xyz yx;
    struct xyz zy;
    struct xyz u, v;

    // better have 4 atoms exactly
    vsub2(wx,new_position[jig->atoms[0]->index],
          new_position[jig->atoms[1]->index]);
    vsub2(yx,new_position[jig->atoms[2]->index],
          new_position[jig->atoms[1]->index]);
    vsub2(xy,new_position[jig->atoms[1]->index],
          new_position[jig->atoms[2]->index]);
    vsub2(zy,new_position[jig->atoms[3]->index],
          new_position[jig->atoms[2]->index]);
    // vx = cross product
    u = vx(wx, yx);
    v = vx(xy, zy);
    if (vdot(zy, u) < 0) {
	jig->data = -angleBetween(u, v);
    } else {
	jig->data = angleBetween(u, v);
    }
}


void
jigAngle(struct jig *jig, struct xyz *new_position)
{
    struct xyz v1;
    struct xyz v2;

    // better have 3 atoms exactly
    vsub2(v1,new_position[jig->atoms[0]->index],
          new_position[jig->atoms[1]->index]);
    vsub2(v2,new_position[jig->atoms[2]->index],
          new_position[jig->atoms[1]->index]);
    // jig->data = acos(vdot(v1,v2)/(vlen(v1)*vlen(v2)));
    jig->data = angleBetween(v1, v2);
}


void
jigRadius(struct jig *jig, struct xyz *new_position)
{
    struct xyz v1;

    // better have 2 atoms exactly
    vsub2(v1,new_position[jig->atoms[0]->index],
          new_position[jig->atoms[1]->index]);

    jig->data = vlen(v1);
}

/*
 * Local Variables:
 * c-basic-offset: 4
 * tab-width: 8
 * End:
 */