codingPracticesSSW
codingPracticesSSW
Engineering 101/201
Scientific Software Club 2/13/17
Papers
● Best Practices for Scientific Computing, Wilson et al.
● Good Enough Practices in Scientific Computing, Wilson et al.
● Barely Sufficient Software Engineering: 10 Practices to Improve your CSE
Software, Heroux and Willenbring
Misconception: Coding is unimportant! It’s not like I’m a software
engineer...
(The crucial part is getting the numerical algorithm, proper data, good results, etc)
The (Relative) Truth: Coding is an important part of research and
a skill that takes years to hone
~~~~INPUT~~~~
data: N x K time domain signal, N = number samples, K = dimension of data
p: includes measurement frequency in Hz, model size to fit
~~~~~~~~~~~~~
~~~OUTPUT~~~
Fitted system model, saved in results folder as system.csv
~~~~~~~~~~~~~
*/
void runN4SID(double data, params p){
…
}
Name Intelligently
● Fits in with earlier example, but having descriptive function and variables
is extremely important
● A headache for numerical calculations
○ Generally, code might be ugly, but make sure function is named well!
Name Variables Intelligently
void calcStuff(...){
A = getMatrix(...);
[U, D, V] = svd(A);
[X, Y] = getData(...);
[E, Z] = eig(X*A*Y);
w = getWeights...();
[S, N] = sumEV(W, w);
B = convolveMatrix(A, N, S)
I = [ identity(N); identity(N)];
C = I*B + I*A;
[Q, R, P] = qr(C);
….
(you get the point)
}
class Central2D { float& fx2(int ix, int iy) { return fx2_[offset(ix,iy)]; }
public: float& fx3(int ix, int iy) { return fx3_[offset(ix,iy)]; }
Central2D(float w, float h, // Domain width / height float& gy1(int ix, int iy) { return gy1_[offset(ix,iy)]; } // y differences of g
int nx, int ny, // Number of cells in x/y (without ghosts) float& gy2(int ix, int iy) { return gy2_[offset(ix,iy)]; }
float cfl = 0.45) : // Max allowed CFL number float& gy3(int ix, int iy) { return gy3_[offset(ix,iy)]; }
nx(nx), ny(ny), float& v1(int ix, int iy) {return v1_[offset(ix,iy)]; } // Solution values at next
nx_all(nx + 2*nghost), float& v2(int ix, int iy) {return v2_[offset(ix,iy)]; }
ny_all(ny + 2*nghost), float& v3(int ix, int iy) {return v3_[offset(ix,iy)]; }
dx(w/nx), dy(h/ny),
cfl(cfl) {} // Diagnostics
void solution_check();
static constexpr int nghost = 3; // Number of ghost cells // Array size accessors
const int nx, ny; // Number of (non-ghost) cells in x/y int xsize() const { return nx; }
const int nx_all, ny_all; // Total cells in x/y (including ghost) int ysize() const { return ny; }
const float dx, dy; // Cell size in x/y
const float cfl; // Allowed CFL number // Read / write elements of simulation state
// Array accessor functions float& operator()(int i, int j) {
int offset(int ix, int iy) const { return iy*nx_all+ix; } return u1_[offset(i,j)];
}
float& u1(int ix, int iy) { return u1_[offset(ix,iy)]; } // Solution values
float& u2(int ix, int iy) { return u2_[offset(ix,iy)]; } const float& operator()(int i, int j) const {
float& u3(int ix, int iy) { return u3_[offset(ix,iy)]; } return u1_[offset(i,j)];
float& f1(int ix, int iy) { return f1_[offset(ix,iy)]; } // Fluxes in x }
float& f2(int ix, int iy) { return f2_[offset(ix,iy)]; } // Wrapped accessor (periodic BC)
float& f3(int ix, int iy) { return f3_[offset(ix,iy)]; } int ioffset(int ix, int iy) {
float& g1(int ix, int iy) { return g1_[offset(ix,iy)]; } // Fluxes in y return offset( (ix+nx-nghost) % nx + nghost,
float& g2(int ix, int iy) { return g2_[offset(ix,iy)]; } (iy+ny-nghost) % ny + nghost );
float& g3(int ix, int iy) { return g3_[offset(ix,iy)]; } }
float& ux1(int ix, int iy) { return ux1_[offset(ix,iy)]; } // x differences of u
float& ux2(int ix, int iy) { return ux2_[offset(ix,iy)]; } float& uwrap1(int ix, int iy) { return u1_[ioffset(ix,iy)]; }
float& ux3(int ix, int iy) { return ux3_[offset(ix,iy)]; } float& uwrap2(int ix, int iy) { return u2_[ioffset(ix,iy)]; }
float& uy1(int ix, int iy) { return uy1_[offset(ix,iy)]; } // y differences of u float& uwrap3(int ix, int iy) { return u3_[ioffset(ix,iy)]; }
float& uy2(int ix, int iy) { return uy2_[offset(ix,iy)]; }
float& uy3(int ix, int iy) { return uy3_[offset(ix,iy)]; }
float& fx1(int ix, int iy) { return fx1_[offset(ix,iy)]; } // x differences of f void run(float tfinal);
// Call f(Uxy, x, y) at each cell center to set initial conditions
Decompose Programs into Functions
● Try to keep functions short
● Modularity makes code base more flexible, more easily modifiable
● Saves lines of code
● Practically speaking, humans can only remember a few things at a time!
Decomposing Programs into Functions
void calcStuff(...){ void calcStuff(...){
Node root; Node root;
… …
Node data; Node data;
… …
bool checkchild = 0; bool checkchild = isChild(root, data);
for(i = 0; i < root.numchildren; i++){ ...
if(root.child[i] == data){ }
checkchild = 1;
}
}
...
}
Eliminate Duplication
double calcValues(...){ double calcValues(... , bool Filter){
… …
X = getvalue(...); X = getvalue(...);
return X; if( Filter == true){
} X = filter(X);
VS }
double calcValuesFilter(...){ return x;
… }
X = getvalue(...);
X = filter(X);
return X;
}
Keep Semantics Consistent
void scaleVec(vec v, double n){ void scaleMatrix(double n, matrix m){
... ...
} }