1 /*
2  * Copyright (c) 2016, The University of Oxford
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * 1. Redistributions of source code must retain the above copyright notice,
8  *    this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  *    this list of conditions and the following disclaimer in the documentation
11  *    and/or other materials provided with the distribution.
12  * 3. Neither the name of the University of Oxford nor the names of its
13  *    contributors may be used to endorse or promote products derived from this
14  *    software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <Python.h>
30 
31 /* http://docs.scipy.org/doc/numpy-dev/reference/c-api.deprecations.html */
32 #define NPY_NO_DEPRECATED_API NPY_1_8_API_VERSION
33 #include <numpy/arrayobject.h>
34 #include <stdio.h>
35 #include <math.h>
36 #include <string.h>
37 #include <stddef.h>
38 
39 #ifndef M_PI
40 #define M_PI 3.14159265358979323846264338327950288
41 #endif
42 
43 typedef struct DComplex {
44     double re;
45     double im;
46 } DComplex;
47 
48 typedef struct Complex {
49     float re;
50     float im;
51 } Complex;
52 
53 
54 /**
55  * @brief
56  * @details
57  */
apply_gains(PyObject * self,PyObject * args)58 static PyObject* apply_gains(PyObject* self, PyObject* args)
59 {
60     /* Read input arguments */
61     PyObject *vis_in_ = 0, *gains_ = 0;
62     PyObject *pyo_vis_in = 0, *pyo_vis_out = 0, *pyo_gains = 0;
63     if (!PyArg_ParseTuple(args, "OO", &vis_in_, &gains_))
64         return 0;
65 
66     /* Return an ndarray from the python objects */
67     pyo_vis_in = PyArray_FROM_OTF(vis_in_, NPY_CDOUBLE, NPY_ARRAY_IN_ARRAY);
68     pyo_gains = PyArray_FROM_OTF(gains_, NPY_CDOUBLE, NPY_ARRAY_IN_ARRAY);
69     if (!pyo_vis_in || !pyo_gains) goto fail;
70 
71     /* TODO(BM) Error checking on the dims! */
72     int nd_vis = PyArray_NDIM((PyArrayObject*)pyo_vis_in);
73     npy_intp* vis_dims = PyArray_DIMS((PyArrayObject*)pyo_vis_in);
74     /*int num_vis = (int) vis_dims[0];*/
75 
76     /*int nd_gains = PyArray_NDIM((PyArrayObject*)pyo_gains);*/
77     npy_intp* gains_dims = PyArray_DIMS((PyArrayObject*)pyo_gains);
78     int num_antennas = (int) gains_dims[0];
79 
80     /* Create PyObject for output visibilities */
81     pyo_vis_out = PyArray_SimpleNew(nd_vis, vis_dims, NPY_CDOUBLE);
82 
83     /* Apply the gains: v_out = gp * v_in * conj(gq) */
84     DComplex* v_out = (DComplex*)PyArray_DATA((PyArrayObject*)pyo_vis_out);
85     DComplex* g = (DComplex*)PyArray_DATA((PyArrayObject*)pyo_gains);
86     DComplex* v_in = (DComplex*)PyArray_DATA((PyArrayObject*)pyo_vis_in);
87     for (int i = 0, p = 0; p < num_antennas; ++p) {
88         for (int q = p + 1; q < num_antennas; ++q, ++i) {
89             double a = v_in[i].re * g[p].re - v_in[i].im * g[p].im;
90             double b = v_in[i].im * g[p].re + v_in[i].re * g[p].im;
91             v_out[i].re = a * g[q].re + b * g[q].im;
92             v_out[i].im = b * g[q].re - a * g[q].im;
93         }
94     }
95     /* Decrement references to temporary array objects. */
96     Py_XDECREF(pyo_vis_in);
97     Py_XDECREF(pyo_gains);
98     return Py_BuildValue("N", pyo_vis_out);
99 
100     /*
101     printf("  - Ref count: %zi, %zi, %zi\n",
102             PyArray_REFCOUNT(pyo_vis_in),
103             PyArray_REFCOUNT(pyo_gains),
104             PyArray_REFCOUNT(pyo_vis_out));
105     */
106 
107 fail:
108     Py_XDECREF(pyo_gains);
109     Py_XDECREF(pyo_vis_in);
110     Py_XDECREF(pyo_vis_out);
111     return 0;
112 }
113 
114 
115 /**
116  * @brief
117  * @details
118  */
apply_gains_2(PyObject * self,PyObject * args)119 static PyObject* apply_gains_2(PyObject* self, PyObject* args)
120 {
121     /* Read input arguments */
122     PyObject *vis_in_o = 0, *gains_o = 0;
123     PyObject *pyo_vis_in = 0, *pyo_vis_out = 0, *pyo_gains = 0;
124     if (!PyArg_ParseTuple(args, "OO", &vis_in_o, &gains_o))
125         return 0;
126 
127     /* Return an ndarray from the python objects */
128     pyo_vis_in = PyArray_FROM_OTF(vis_in_o, NPY_CFLOAT, NPY_ARRAY_IN_ARRAY);
129     pyo_gains = PyArray_FROM_OTF(gains_o, NPY_CFLOAT, NPY_ARRAY_IN_ARRAY);
130     if (!pyo_vis_in || !pyo_gains) goto fail;
131 
132     /* TODO(BM) Error checking on the dims! */
133     int nd_vis = PyArray_NDIM((PyArrayObject*)pyo_vis_in);
134     npy_intp* vis_dims = PyArray_DIMS((PyArrayObject*)pyo_vis_in);
135     /*int num_vis = (int) vis_dims[0];*/
136 
137     /*int nd_gains = PyArray_NDIM((PyArrayObject*)pyo_gains);*/
138     npy_intp* gains_dims = PyArray_DIMS((PyArrayObject*)pyo_gains);
139     int num_antennas = (int) gains_dims[0];
140 
141     /* Create PyObject for output visibilities */
142     pyo_vis_out = PyArray_SimpleNew(nd_vis, vis_dims, NPY_CFLOAT);
143 
144     /* Apply the gains: v_out = gp * v_in * conj(gq) */
145     Complex* v_out = (Complex*)PyArray_DATA((PyArrayObject*)pyo_vis_out);
146     Complex* g = (Complex*)PyArray_DATA((PyArrayObject*)pyo_gains);
147     Complex* v_in = (Complex*)PyArray_DATA((PyArrayObject*)pyo_vis_in);
148     for (int i = 0, p = 0; p < num_antennas; ++p) {
149         for (int q = p + 1; q < num_antennas; ++q, ++i) {
150             float a = v_in[i].re * g[p].re - v_in[i].im * g[p].im;
151             float b = v_in[i].im * g[p].re + v_in[i].re * g[p].im;
152             v_out[i].re = a * g[q].re + b * g[q].im;
153             v_out[i].im = b * g[q].re - a * g[q].im;
154         }
155     }
156     /* Decrement references to temporary array objects. */
157     Py_XDECREF(pyo_vis_in);
158     Py_XDECREF(pyo_gains);
159     return Py_BuildValue("N", pyo_vis_out);
160 
161 fail:
162     Py_XDECREF(pyo_gains);
163     Py_XDECREF(pyo_vis_in);
164     Py_XDECREF(pyo_vis_out);
165     return 0;
166 }
167 
168 
169 
170 /**
171  * @brief
172  * @details
173  */
vis_list_to_matrix(PyObject * self,PyObject * args)174 static PyObject* vis_list_to_matrix(PyObject* self, PyObject* args)
175 {
176     /* Read input arguments */
177     PyObject* vis_list_ = NULL;
178     int num_ant = 0;
179     if (!PyArg_ParseTuple(args, "Oi", &vis_list_, &num_ant))
180         return NULL;
181 
182     /* Convert to an ndarray */
183     PyObject* pyo_vis_list = PyArray_FROM_OTF(vis_list_, NPY_CDOUBLE,
184                                               NPY_ARRAY_IN_ARRAY);
185     if (!pyo_vis_list) goto fail;
186 
187     /* TODO(BM) Error checking on the dims!
188     int nd_vis = PyArray_NDIM((PyArrayObject*)pyo_vis_list);
189     npy_intp* vis_dims = PyArray_DIMS((PyArrayObject*)pyo_vis_list);
190     int num_vis = vis_dims[0];*/
191 
192     /* Create PyObject for output visibilities */
193     npy_intp dims[] = { num_ant, num_ant };
194     PyObject* pyo_vis_matrix = PyArray_SimpleNew(2, dims, NPY_CDOUBLE);
195 
196     DComplex* v_list = (DComplex*)PyArray_DATA((PyArrayObject*)pyo_vis_list);
197     DComplex* v_matrix = (DComplex*)PyArray_DATA((PyArrayObject*)pyo_vis_matrix);
198     for (int i = 0, p = 0; p < num_ant; ++p) {
199         for (int q = p + 1; q < num_ant; ++q, ++i) {
200             v_matrix[p * num_ant + q].re = v_list[i].re;
201             v_matrix[p * num_ant + q].im = -v_list[i].im;
202             v_matrix[q * num_ant + p].re = v_list[i].re;
203             v_matrix[q * num_ant + p].im = v_list[i].im;
204         }
205     }
206     for (int i = 0; i < num_ant; ++i) {
207         v_matrix[i * num_ant + i].re = 0.0;
208         v_matrix[i * num_ant + i].im = 0.0;
209     }
210 
211     /* Decrement references to temporary array objects. */
212     Py_XDECREF(pyo_vis_list);
213     return Py_BuildValue("N", pyo_vis_matrix);
214 
215 fail:
216     Py_XDECREF(pyo_vis_list);
217     return 0;
218 }
219 
220 
221 /**
222  * @brief
223  * @details
224  */
vis_list_to_matrix_2(PyObject * self,PyObject * args)225 static PyObject* vis_list_to_matrix_2(PyObject* self, PyObject* args)
226 {
227     /* Read input arguments */
228     PyObject* vis_list_ = NULL;
229     int num_ant = 0;
230     if (!PyArg_ParseTuple(args, "Oi", &vis_list_, &num_ant))
231         return NULL;
232 
233     /* Convert to an ndarray */
234     PyObject* pyo_vis_list = PyArray_FROM_OTF(vis_list_, NPY_CFLOAT,
235                                               NPY_ARRAY_IN_ARRAY);
236     if (!pyo_vis_list) goto fail;
237 
238     /* TODO(BM) Error checking on the dims!
239     int nd_vis = PyArray_NDIM((PyArrayObject*)pyo_vis_list);
240     npy_intp* vis_dims = PyArray_DIMS((PyArrayObject*)pyo_vis_list);
241     int num_vis = vis_dims[0];*/
242 
243     /* Create PyObject for output visibilities */
244     npy_intp dims[] = { num_ant, num_ant };
245     PyObject* pyo_vis_matrix = PyArray_SimpleNew(2, dims, NPY_CFLOAT);
246 
247     Complex* v_list = (Complex*)PyArray_DATA((PyArrayObject*)pyo_vis_list);
248     Complex* v_matrix = (Complex*)PyArray_DATA((PyArrayObject*)pyo_vis_matrix);
249     for (int i = 0, p = 0; p < num_ant; ++p) {
250         for (int q = p + 1; q < num_ant; ++q, ++i) {
251             v_matrix[p * num_ant + q].re = v_list[i].re;
252             v_matrix[p * num_ant + q].im = -v_list[i].im;
253             v_matrix[q * num_ant + p].re = v_list[i].re;
254             v_matrix[q * num_ant + p].im = v_list[i].im;
255         }
256     }
257     for (int i = 0; i < num_ant; ++i) {
258         v_matrix[i * num_ant + i].re = 0.0;
259         v_matrix[i * num_ant + i].im = 0.0;
260     }
261 
262     /* Decrement references to temporary array objects. */
263     Py_XDECREF(pyo_vis_list);
264     return Py_BuildValue("N", pyo_vis_matrix);
265 
266 fail:
267     Py_XDECREF(pyo_vis_list);
268     return 0;
269 }
270 
271 
272 
check_ref_count(PyObject * self,PyObject * args)273 static PyObject* check_ref_count(PyObject* self, PyObject* args)
274 {
275     PyObject* obj = NULL;
276     /* https://docs.python.org/2/c-api/arg.html */
277     /* Reference count is not increased by 'O' for PyArg_ParseTyple */
278     if (!PyArg_ParseTuple(args, "O", &obj))
279         return NULL;
280     return Py_BuildValue("ii", PyArray_REFCOUNT(obj), Py_REFCNT(obj));
281 }
282 
283 
expand(PyObject * self,PyObject * args)284 static PyObject* expand(PyObject* self, PyObject* args)
285 {
286     PyObject *vis_in_comp = 0, *vis_out_orig = 0;
287     PyArrayObject *ant1_, *ant2_, *weight_, *data_in_, *data_out_;
288     int num_antennas = 0, num_baselines = 0, num_input_vis = 0;
289     int *ant1, *ant2, a1, a2, b, t, row_in, row_out, *out_time_idx = 0, w;
290     double *data_in, *data_out, *weight, d[2];
291     const char *input_name, *output_name;
292     if (!PyArg_ParseTuple(args, "iOsOs", &num_antennas,
293             &vis_in_comp, &input_name, &vis_out_orig, &output_name))
294         return 0;
295     num_baselines = num_antennas * (num_antennas - 1) / 2;
296     ant1_     = (PyArrayObject*)PyDict_GetItemString(vis_in_comp, "antenna1");
297     ant2_     = (PyArrayObject*)PyDict_GetItemString(vis_in_comp, "antenna2");
298     weight_   = (PyArrayObject*)PyDict_GetItemString(vis_in_comp, "weight");
299     data_in_  = (PyArrayObject*)PyDict_GetItemString(vis_in_comp, input_name);
300     data_out_ = (PyArrayObject*)PyDict_GetItemString(vis_out_orig, output_name);
301     num_input_vis = (int)*PyArray_DIMS(data_in_);
302     ant1     = (int*) PyArray_DATA(ant1_);
303     ant2     = (int*) PyArray_DATA(ant2_);
304     weight   = (double*) PyArray_DATA(weight_);
305     data_in  = (double*) PyArray_DATA(data_in_);
306     data_out = (double*) PyArray_DATA(data_out_);
307     out_time_idx = (int*) calloc(num_baselines, sizeof(int));
308 
309     for (row_in = 0; row_in < num_input_vis; ++row_in)
310     {
311         a1 = ant1[row_in];
312         a2 = ant2[row_in];
313         w = (int) round(weight[row_in]);
314         d[0] = data_in[2*row_in + 0];
315         d[1] = data_in[2*row_in + 1];
316         b = a1 * (num_antennas - 1) - (a1 - 1) * a1 / 2 + a2 - a1 - 1;
317         for (t = out_time_idx[b]; t < out_time_idx[b] + w; ++t)
318         {
319             row_out = t * num_baselines + b;
320             data_out[2*row_out + 0] = d[0];
321             data_out[2*row_out + 1] = d[1];
322         }
323         out_time_idx[b] += w;
324     }
325 
326     free(out_time_idx);
327     return Py_BuildValue("");
328 }
329 
330 struct oskar_BDA
331 {
332     /* Options. */
333     int num_antennas, num_baselines, num_pols, num_times;
334     double duvw_max, dt_max, max_fact, fov_deg, delta_t;
335 
336     /* Output data. */
337     int bda_row, output_size, *ant1, *ant2;
338     double *u, *v, *w, *data, *time, *exposure, *sigma, *weight;
339 
340     /* Current UVW coordinates. */
341     double *uu_current, *vv_current, *ww_current;
342 
343     /* Buffers of deltas along the baseline in the current average. */
344     double *duvw, *dt;
345 
346     /* Buffers of the current average along the baseline. */
347     double *ave_uu, *ave_vv, *ave_ww, *ave_data;
348     int *ave_count;
349 };
350 typedef struct oskar_BDA oskar_BDA;
351 
352 static const char* name = "oskar_BDA";
353 
354 #define INT sizeof(int)
355 #define DBL sizeof(double)
356 
oskar_bda_clear(oskar_BDA * h)357 static void oskar_bda_clear(oskar_BDA* h)
358 {
359     /* Clear averages. */
360     memset(h->uu_current, '\0', h->num_baselines * DBL);
361     memset(h->vv_current, '\0', h->num_baselines * DBL);
362     memset(h->ww_current, '\0', h->num_baselines * DBL);
363     memset(h->duvw, '\0', h->num_baselines * DBL);
364     memset(h->dt, '\0', h->num_baselines * DBL);
365     memset(h->ave_uu, '\0', h->num_baselines * DBL);
366     memset(h->ave_vv, '\0', h->num_baselines * DBL);
367     memset(h->ave_ww, '\0', h->num_baselines * DBL);
368     memset(h->ave_count, '\0', h->num_baselines * INT);
369     memset(h->ave_data, '\0', h->num_baselines * h->num_pols * 2*DBL);
370 
371     /* Free the output data. */
372     h->output_size = 0;
373     h->bda_row = 0;
374     free(h->ant1);
375     free(h->ant2);
376     free(h->u);
377     free(h->v);
378     free(h->w);
379     free(h->data);
380     free(h->time);
381     free(h->exposure);
382     free(h->sigma);
383     free(h->weight);
384     h->ant1 = 0;
385     h->ant2 = 0;
386     h->u = 0;
387     h->v = 0;
388     h->w = 0;
389     h->data = 0;
390     h->time = 0;
391     h->exposure = 0;
392     h->sigma = 0;
393     h->weight = 0;
394 }
395 
396 
oskar_bda_free(oskar_BDA * h)397 static void oskar_bda_free(oskar_BDA* h)
398 {
399     oskar_bda_clear(h);
400     free(h->uu_current);
401     free(h->vv_current);
402     free(h->ww_current);
403     free(h->duvw);
404     free(h->dt);
405     free(h->ave_uu);
406     free(h->ave_vv);
407     free(h->ave_ww);
408     free(h->ave_data);
409     free(h->ave_count);
410     free(h);
411 }
412 
413 
414 /* arcsinc(x) function from Obit. Uses Newton-Raphson method.  */
inv_sinc(double value)415 static double inv_sinc(double value)
416 {
417     double x1 = 0.001;
418     for (int i = 0; i < 1000; ++i)
419     {
420         double x0 = x1;
421         double a = x0 * M_PI;
422         x1 = x0 - ((sin(a) / a) - value) /
423                         ((a * cos(a) - M_PI * sin(a)) / (a * a));
424         if (fabs(x1 - x0) < 1.0e-6)
425             break;
426     }
427     return x1;
428 }
429 
430 
bda_free(PyObject * capsule)431 static void bda_free(PyObject* capsule)
432 {
433     oskar_BDA* h = (oskar_BDA*) PyCapsule_GetPointer(capsule, name);
434     oskar_bda_free(h);
435 }
436 
437 
get_handle(PyObject * capsule)438 static oskar_BDA* get_handle(PyObject* capsule)
439 {
440     oskar_BDA* h = 0;
441     if (!PyCapsule_CheckExact(capsule))
442     {
443         PyErr_SetString(PyExc_RuntimeError, "Object is not a PyCapsule.");
444         return 0;
445     }
446     h = (oskar_BDA*) PyCapsule_GetPointer(capsule, name);
447     if (!h)
448     {
449         PyErr_SetString(PyExc_RuntimeError,
450                 "Capsule is not of type oskar_BDA.");
451         return 0;
452     }
453     return h;
454 }
455 
456 
bda_create(PyObject * self,PyObject * args)457 static PyObject* bda_create(PyObject* self, PyObject* args)
458 {
459     oskar_BDA* h = 0;
460     PyObject* capsule = 0;
461     int num_antennas, num_baselines, num_pols;
462     if (!PyArg_ParseTuple(args, "ii", &num_antennas, &num_pols))
463         return 0;
464 
465     /* Create and initialise the BDA object. */
466     h = (oskar_BDA*) calloc(1, sizeof(oskar_BDA));
467     num_baselines = num_antennas * (num_antennas - 1) / 2;
468     h->num_antennas  = num_antennas;
469     h->num_baselines = num_baselines;
470     h->num_pols      = num_pols;
471     h->uu_current    = (double*) calloc(num_baselines, DBL);
472     h->vv_current    = (double*) calloc(num_baselines, DBL);
473     h->ww_current    = (double*) calloc(num_baselines, DBL);
474     h->duvw          = (double*) calloc(num_baselines, DBL);
475     h->dt            = (double*) calloc(num_baselines, DBL);
476     h->ave_uu        = (double*) calloc(num_baselines, DBL);
477     h->ave_vv        = (double*) calloc(num_baselines, DBL);
478     h->ave_ww        = (double*) calloc(num_baselines, DBL);
479     h->ave_count     = (int*)    calloc(num_baselines, INT);
480     h->ave_data      = (double*) calloc(num_baselines * num_pols, 2*DBL);
481 
482     /* Initialise and return the PyCapsule. */
483     capsule = PyCapsule_New((void*)h, name, (PyCapsule_Destructor)bda_free);
484     return Py_BuildValue("N", capsule); /* Don't increment refcount. */
485 }
486 
487 
bda_set_compression(PyObject * self,PyObject * args)488 static PyObject* bda_set_compression(PyObject* self, PyObject* args)
489 {
490     oskar_BDA* h = 0;
491     PyObject* capsule = 0;
492     double max_fact = 0.0, fov_deg = 0.0, wavelength = 0.0, dt_max = 0.0;
493     if (!PyArg_ParseTuple(args, "Odddd",
494             &capsule, &max_fact, &fov_deg, &wavelength, &dt_max))
495         return 0;
496     if (!(h = get_handle(capsule))) return 0;
497     h->max_fact = max_fact;
498     h->fov_deg = fov_deg;
499     h->duvw_max = inv_sinc(1.0 / max_fact) / (fov_deg * (M_PI / 180.0));
500     h->duvw_max *= wavelength;
501     h->dt_max = dt_max;
502     return Py_BuildValue("d", h->duvw_max);
503 }
504 
505 
bda_set_delta_t(PyObject * self,PyObject * args)506 static PyObject* bda_set_delta_t(PyObject* self, PyObject* args)
507 {
508     oskar_BDA* h = 0;
509     PyObject* capsule = 0;
510     double delta_t = 0.0;
511     if (!PyArg_ParseTuple(args, "Od", &capsule, &delta_t)) return 0;
512     if (!(h = get_handle(capsule))) return 0;
513     h->delta_t = delta_t;
514     return Py_BuildValue("");
515 }
516 
517 
bda_set_num_times(PyObject * self,PyObject * args)518 static PyObject* bda_set_num_times(PyObject* self, PyObject* args)
519 {
520     oskar_BDA* h = 0;
521     PyObject* capsule = 0;
522     int num_times = 0;
523     if (!PyArg_ParseTuple(args, "Oi", &capsule, &num_times)) return 0;
524     if (!(h = get_handle(capsule))) return 0;
525     h->num_times = num_times;
526     return Py_BuildValue("");
527 }
528 
529 
bda_set_initial_coords(PyObject * self,PyObject * args)530 static PyObject* bda_set_initial_coords(PyObject* self, PyObject* args)
531 {
532     oskar_BDA* h = 0;
533     PyObject *obj[] = {0, 0, 0, 0};
534     PyArrayObject *uu_ = 0, *vv_ = 0, *ww_ = 0;
535     if (!PyArg_ParseTuple(args, "OOOO",
536             &obj[0], &obj[1], &obj[2], &obj[3]))
537         return 0;
538     if (!(h = get_handle(obj[0]))) return 0;
539 
540     /* Make sure input objects are arrays. Convert if required. */
541     uu_ = (PyArrayObject*) PyArray_FROM_OTF(obj[1],
542             NPY_DOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
543     if (!uu_) goto fail;
544     vv_ = (PyArrayObject*) PyArray_FROM_OTF(obj[2],
545             NPY_DOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
546     if (!vv_) goto fail;
547     ww_ = (PyArrayObject*) PyArray_FROM_OTF(obj[3],
548             NPY_DOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
549     if (!ww_) goto fail;
550 
551     /* Check dimensions. */
552     if (PyArray_NDIM(uu_) != 1 || PyArray_NDIM(vv_) != 1 ||
553             PyArray_NDIM(ww_) != 1)
554     {
555         PyErr_SetString(PyExc_RuntimeError, "Coordinate arrays must be 1D.");
556         goto fail;
557     }
558     if (h->num_baselines != (int)*PyArray_DIMS(uu_) ||
559             h->num_baselines != (int)*PyArray_DIMS(vv_) ||
560             h->num_baselines != (int)*PyArray_DIMS(ww_))
561     {
562         PyErr_SetString(PyExc_RuntimeError, "Input data dimension mismatch.");
563         goto fail;
564     }
565 
566     /* Copy the data. */
567     memcpy(h->uu_current, PyArray_DATA(uu_), h->num_baselines * DBL);
568     memcpy(h->vv_current, PyArray_DATA(vv_), h->num_baselines * DBL);
569     memcpy(h->ww_current, PyArray_DATA(ww_), h->num_baselines * DBL);
570 
571     Py_XDECREF(uu_);
572     Py_XDECREF(vv_);
573     Py_XDECREF(ww_);
574     return Py_BuildValue("");
575 
576 fail:
577     Py_XDECREF(uu_);
578     Py_XDECREF(vv_);
579     Py_XDECREF(ww_);
580     return 0;
581 }
582 
583 
bda_add_data(PyObject * self,PyObject * args)584 static PyObject* bda_add_data(PyObject* self, PyObject* args)
585 {
586     oskar_BDA* h = 0;
587     PyObject *obj[] = {0, 0, 0, 0, 0};
588     PyArrayObject *amp_current_ = 0;
589     PyArrayObject *uu_next_ = 0, *vv_next_ = 0, *ww_next_ = 0;
590     int a1, a2, b, i, j, p, t = 0;
591     double *uu_next = 0, *vv_next = 0, *ww_next = 0, *data;
592     if (!PyArg_ParseTuple(args, "OiOOOO", &obj[0], &t, &obj[1],
593             &obj[2], &obj[3], &obj[4])) return 0;
594     if (!(h = get_handle(obj[0]))) return 0;
595 
596     /* Get array handles. */
597     amp_current_ = (PyArrayObject*) PyArray_FROM_OTF(obj[1],
598             NPY_CDOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
599     if (!amp_current_) goto fail;
600     if ((obj[2] != Py_None) && (obj[3] != Py_None) && (obj[4] != Py_None))
601     {
602         /* Make sure input objects are arrays. Convert if required. */
603         uu_next_ = (PyArrayObject*) PyArray_FROM_OTF(obj[2],
604                 NPY_DOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
605         if (!uu_next_) goto fail;
606         vv_next_ = (PyArrayObject*) PyArray_FROM_OTF(obj[3],
607                 NPY_DOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
608         if (!vv_next_) goto fail;
609         ww_next_ = (PyArrayObject*) PyArray_FROM_OTF(obj[4],
610                 NPY_DOUBLE, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST);
611         if (!ww_next_) goto fail;
612 
613         /* Check dimensions. */
614         if (PyArray_NDIM(uu_next_) != 1 ||
615                 PyArray_NDIM(vv_next_) != 1 ||
616                 PyArray_NDIM(ww_next_) != 1)
617         {
618             PyErr_SetString(PyExc_RuntimeError,
619                     "Coordinate arrays must be 1D.");
620             goto fail;
621         }
622         if (h->num_baselines != (int)*PyArray_DIMS(uu_next_) ||
623                 h->num_baselines != (int)*PyArray_DIMS(vv_next_) ||
624                 h->num_baselines != (int)*PyArray_DIMS(ww_next_))
625         {
626             PyErr_SetString(PyExc_RuntimeError,
627                     "Input data dimension mismatch.");
628             goto fail;
629         }
630     }
631 
632     if (uu_next_) uu_next = (double*) PyArray_DATA(uu_next_);
633     if (vv_next_) vv_next = (double*) PyArray_DATA(vv_next_);
634     if (ww_next_) ww_next = (double*) PyArray_DATA(ww_next_);
635     data                  = (double*) PyArray_DATA(amp_current_);
636     for (a1 = 0, b = 0; a1 < h->num_antennas; ++a1)
637     {
638         for (a2 = a1 + 1; a2 < h->num_antennas; ++a2, ++b)
639         {
640             double du, dv, dw, b_duvw = 0.0, s;
641 
642             /* Accumulate into averages. */
643             h->ave_count[b] += 1;
644             h->ave_uu[b]    += h->uu_current[b];
645             h->ave_vv[b]    += h->vv_current[b];
646             h->ave_ww[b]    += h->ww_current[b];
647             for (p = 0; p < h->num_pols; ++p)
648             {
649                 i = b * h->num_pols + p;
650                 h->ave_data[2*i + 0] += data[2*i + 0];
651                 h->ave_data[2*i + 1] += data[2*i + 1];
652             }
653 
654             /* Look ahead to the next point on the baseline and see if
655              * this is also in the average. */
656              if ((t < h->num_times - 1) && uu_next && vv_next && ww_next)
657              {
658                  du = uu_next[b] - h->uu_current[b];
659                  dv = vv_next[b] - h->vv_current[b];
660                  dw = ww_next[b] - h->ww_current[b];
661                  b_duvw = sqrt(du*du + dv*dv + dw*dw);
662              }
663 
664              /* If last time or if next point extends beyond average,
665               * save out averaged data and reset,
666               * else accumulate current average lengths. */
667              if (t == h->num_times - 1 ||
668                      h->duvw[b] + b_duvw > h->duvw_max ||
669                      h->dt[b] + h->delta_t > h->dt_max)
670              {
671                  s = 1.0 / h->ave_count[b];
672                  h->ave_uu[b] *= s;
673                  h->ave_vv[b] *= s;
674                  h->ave_ww[b] *= s;
675                  for (p = 0; p < h->num_pols; ++p)
676                  {
677                      i = b * h->num_pols + p;
678                      h->ave_data[2*i + 0] *= s;
679                      h->ave_data[2*i + 1] *= s;
680                  }
681 
682                  /* Store the averaged data. */
683                  if (h->output_size < h->bda_row + 1)
684                  {
685                      h->output_size += h->num_baselines;
686                      h->ant1     = realloc(h->ant1, h->output_size * INT);
687                      h->ant2     = realloc(h->ant2, h->output_size * INT);
688                      h->u        = realloc(h->u, h->output_size * DBL);
689                      h->v        = realloc(h->v, h->output_size * DBL);
690                      h->w        = realloc(h->w, h->output_size * DBL);
691                      h->exposure = realloc(h->exposure, h->output_size * DBL);
692                      h->sigma    = realloc(h->sigma, h->output_size * DBL);
693                      h->weight   = realloc(h->weight, h->output_size * DBL);
694                      h->data     = realloc(h->data,
695                              h->num_pols * h->output_size * 2*DBL);
696                  }
697                  h->ant1[h->bda_row] = a1;
698                  h->ant2[h->bda_row] = a2;
699                  h->u[h->bda_row] = h->ave_uu[b];
700                  h->v[h->bda_row] = h->ave_vv[b];
701                  h->w[h->bda_row] = h->ave_ww[b];
702                  h->exposure[h->bda_row] = h->ave_count[b] * h->delta_t;
703                  h->sigma[h->bda_row] = sqrt(s);
704                  h->weight[h->bda_row] = h->ave_count[b];
705                  for (p = 0; p < h->num_pols; ++p)
706                  {
707                      i = b * h->num_pols + p;
708                      j = h->bda_row * h->num_pols + p;
709                      h->data[2*j + 0] = h->ave_data[2*i + 0];
710                      h->data[2*j + 1] = h->ave_data[2*i + 1];
711                  }
712 
713                  /* Reset baseline accumulation buffers. */
714                  h->duvw[b] = 0.0;
715                  h->dt[b] = 0.0;
716                  h->ave_count[b] = 0;
717                  h->ave_uu[b] = 0.0;
718                  h->ave_vv[b] = 0.0;
719                  h->ave_ww[b] = 0.0;
720                  for (p = 0; p < h->num_pols; ++p)
721                  {
722                      i = b * h->num_pols + p;
723                      h->ave_data[2*i + 0] = 0.0;
724                      h->ave_data[2*i + 1] = 0.0;
725                  }
726 
727                  /* Update baseline average row counter for next output. */
728                  h->bda_row += 1;
729              }
730              else
731              {
732                  /* Accumulate distance and time on current baseline. */
733                  h->duvw[b] += b_duvw;
734                  h->dt[b] += h->delta_t;
735              }
736         }
737     } /* end baseline loop */
738 
739     /* Copy "next" to "current" for the next time block. */
740     if (uu_next) memcpy(h->uu_current, uu_next, h->num_baselines * DBL);
741     if (vv_next) memcpy(h->vv_current, vv_next, h->num_baselines * DBL);
742     if (ww_next) memcpy(h->ww_current, ww_next, h->num_baselines * DBL);
743 
744     Py_XDECREF(amp_current_);
745     Py_XDECREF(uu_next_);
746     Py_XDECREF(vv_next_);
747     Py_XDECREF(ww_next_);
748     return Py_BuildValue("");
749 
750 fail:
751     Py_XDECREF(amp_current_);
752     Py_XDECREF(uu_next_);
753     Py_XDECREF(vv_next_);
754     Py_XDECREF(ww_next_);
755     return 0;
756 }
757 
758 
bda_finalise(PyObject * self,PyObject * args)759 static PyObject* bda_finalise(PyObject* self, PyObject* args)
760 {
761     oskar_BDA* h = 0;
762     PyObject* capsule = 0, *dict;
763     PyArrayObject *ant1, *ant2, *uu, *vv, *ww, *data;
764     PyArrayObject *expo, *sigma, *weight;
765     if (!PyArg_ParseTuple(args, "O", &capsule)) return 0;
766     if (!(h = get_handle(capsule))) return 0;
767 
768     /* Create arrays that will be returned to Python. */
769     npy_intp dims1[] = {h->bda_row};
770     npy_intp dims2[] = {h->bda_row * h->num_pols};
771     ant1   = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_INT);
772     ant2   = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_INT);
773     uu     = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_DOUBLE);
774     vv     = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_DOUBLE);
775     ww     = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_DOUBLE);
776     data   = (PyArrayObject*)PyArray_SimpleNew(1, dims2, NPY_CDOUBLE);
777     expo   = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_DOUBLE);
778     sigma  = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_DOUBLE);
779     weight = (PyArrayObject*)PyArray_SimpleNew(1, dims1, NPY_DOUBLE);
780 
781     /* Copy the data into the arrays. */
782     memcpy(PyArray_DATA(ant1), h->ant1, h->bda_row * INT);
783     memcpy(PyArray_DATA(ant2), h->ant2, h->bda_row * INT);
784     memcpy(PyArray_DATA(uu), h->u, h->bda_row * DBL);
785     memcpy(PyArray_DATA(vv), h->v, h->bda_row * DBL);
786     memcpy(PyArray_DATA(ww), h->w, h->bda_row * DBL);
787     memcpy(PyArray_DATA(data), h->data, h->bda_row * h->num_pols * 2*DBL);
788     memcpy(PyArray_DATA(expo), h->exposure, h->bda_row * DBL);
789     memcpy(PyArray_DATA(sigma), h->sigma, h->bda_row * DBL);
790     memcpy(PyArray_DATA(weight), h->weight, h->bda_row * DBL);
791 
792     /* Create a dictionary and return the data in it. */
793     dict = PyDict_New();
794     PyDict_SetItemString(dict, "antenna1", (PyObject*)ant1);
795     PyDict_SetItemString(dict, "antenna2", (PyObject*)ant2);
796     PyDict_SetItemString(dict, "uu", (PyObject*)uu);
797     PyDict_SetItemString(dict, "vv", (PyObject*)vv);
798     PyDict_SetItemString(dict, "ww", (PyObject*)ww);
799     PyDict_SetItemString(dict, "data", (PyObject*)data);
800     PyDict_SetItemString(dict, "exposure", (PyObject*)expo);
801     PyDict_SetItemString(dict, "sigma", (PyObject*)sigma);
802     PyDict_SetItemString(dict, "weight", (PyObject*)weight);
803 
804     /* Clear all current averages. */
805     oskar_bda_clear(h);
806 
807     return Py_BuildValue("N", dict); /* Don't increment refcount. */
808 }
809 
810 
811 /* Method table. */
812 static PyMethodDef methods[] =
813 {
814     {
815         "apply_gains",
816         (PyCFunction)apply_gains, METH_VARARGS,
817         "vis_amp = apply_gains(vis_amp, gains)\n"
818         "Applies gains.\n"
819     },
820     {
821         "apply_gains_2",
822         (PyCFunction)apply_gains_2, METH_VARARGS,
823         "vis_amp = apply_gains_2(vis_amp, gains)\n"
824         "Applies gains.\n"
825     },
826     {
827         "vis_list_to_matrix",
828         (PyCFunction)vis_list_to_matrix, METH_VARARGS,
829         "vis_matrix = vis_list_to_matrix(vis_list)\n"
830         "Converts a list of visibilities to matrix form.\n"
831     },
832     {
833         "vis_list_to_matrix_2",
834         (PyCFunction)vis_list_to_matrix_2, METH_VARARGS,
835         "vis_matrix = vis_list_to_matrix_2(vis_list)\n"
836         "Converts a list of visibilities to matrix form.\n"
837     },
838     {
839         "check_ref_count",
840         (PyCFunction)check_ref_count, METH_VARARGS,
841         "count = check_ref_count(PyObject)\n"
842         "Check the reference count of a python object\n"
843     },
844     {
845         "expand",
846         (PyCFunction)expand, METH_VARARGS,
847         "expand(num_antennas)\n"
848         "Expands BDA data.\n"
849     },
850     {
851         "bda_create",
852         (PyCFunction)bda_create, METH_VARARGS,
853         "handle = bda_create(num_antennas, num_pols)\n"
854         "Creates the BDA object.\n"
855     },
856     {
857         "bda_set_compression",
858         (PyCFunction)bda_set_compression, METH_VARARGS,
859         "bda_set_max_fact(max_fact, fov_deg, wavelength_m, max_avg_time_s)\n"
860         "Sets compression parameters.\n"
861     },
862     {
863         "bda_set_delta_t",
864         (PyCFunction)bda_set_delta_t, METH_VARARGS,
865         "bda_set_delta_t(value)\n"
866         "Sets time interval of input data.\n"
867     },
868     {
869         "bda_set_num_times",
870         (PyCFunction)bda_set_num_times, METH_VARARGS,
871         "bda_set_num_times(value)\n"
872         "Sets number of times in the input data.\n"
873     },
874     {
875         "bda_set_initial_coords",
876         (PyCFunction)bda_set_initial_coords, METH_VARARGS,
877         "bda_set_initial_coords(uu, vv, ww)\n"
878         "Sets initial baseline coordinates.\n"
879     },
880     {
881         "bda_add_data",
882         (PyCFunction)bda_add_data, METH_VARARGS,
883         "bda_add_data(time_index, vis, uu_next, vv_next, ww_next)\n"
884         "Supplies visibility data to be averaged.\n"
885     },
886     {
887         "bda_finalise",
888         (PyCFunction)bda_finalise, METH_VARARGS,
889         "averaged_data = bda_finalise()\n"
890         "Returns averaged visibility data as a dictionary of arrays.\n"
891     },
892     {NULL, NULL, 0, NULL}
893 };
894 
895 #if PY_MAJOR_VERSION >= 3
896 static PyModuleDef moduledef = {
897         PyModuleDef_HEAD_INIT,
898         "_bda_utils",       /* m_name */
899         NULL,               /* m_doc */
900         -1,                 /* m_size */
901         methods             /* m_methods */
902 };
903 #endif
904 
905 
moduleinit(void)906 static PyObject* moduleinit(void)
907 {
908     PyObject* m;
909 #if PY_MAJOR_VERSION >= 3
910     m = PyModule_Create(&moduledef);
911 #else
912     m = Py_InitModule3("_bda_utils", methods, "docstring ...");
913 #endif
914     return m;
915 }
916 
917 #if PY_MAJOR_VERSION >= 3
PyInit__bda_utils(void)918 PyMODINIT_FUNC PyInit__bda_utils(void)
919 {
920     import_array();
921     return moduleinit();
922 }
923 #else
924 // XXX the init function name has to match that of the compiled module
925 // with the pattern 'init<module name>'. This module is called '_apply_gains'
init_bda_utils(void)926 void init_bda_utils(void)
927 {
928     import_array();
929     moduleinit();
930     return;
931 }
932 #endif
933