source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py @ 0101c9f

Last change on this file since 0101c9f was b69b549, checked in by Torin Cooper-Bennun <torin.cooper-bennun@…>, 6 years ago

Merge branch 'ESS_GUI' into ESS_GUI_iss1032

  • Property mode set to 100644
File size: 26.6 KB
Line 
1import copy
2
3from PyQt5 import QtCore
4from PyQt5 import QtGui
5
6import numpy
7
8from sas.qtgui.Plotting.PlotterData import Data1D
9from sas.qtgui.Plotting.PlotterData import Data2D
10
11from sas.qtgui.Perspectives.Fitting.AssociatedComboBox import AssociatedComboBox
12
13model_header_captions = ['Parameter', 'Value', 'Min', 'Max', 'Units']
14
15model_header_tooltips = ['Select parameter for fitting',
16                         'Enter parameter value',
17                         'Enter minimum value for parameter',
18                         'Enter maximum value for parameter',
19                         'Unit of the parameter']
20
21poly_header_captions = ['Parameter', 'PD[ratio]', 'Min', 'Max', 'Npts', 'Nsigs',
22                        'Function', 'Filename']
23
24poly_header_tooltips = ['Select parameter for fitting',
25                        'Enter polydispersity ratio (STD/mean). '
26                        'STD: standard deviation from the mean value',
27                        'Enter minimum value for parameter',
28                        'Enter maximum value for parameter',
29                        'Enter number of points for parameter',
30                        'Enter number of sigmas parameter',
31                        'Select distribution function',
32                        'Select filename with user-definable distribution']
33
34error_tooltip = 'Error value for fitted parameter'
35header_error_caption = 'Error'
36
37def replaceShellName(param_name, value):
38    """
39    Updates parameter name from <param_name>[n_shell] to <param_name>value
40    """
41    assert '[' in param_name
42    return param_name[:param_name.index('[')]+str(value)
43
44def getIterParams(model):
45    """
46    Returns a list of all multi-shell parameters in 'model'
47    """
48    return list([par for par in model.iq_parameters if "[" in par.name])
49
50def getMultiplicity(model):
51    """
52    Finds out if 'model' has multishell parameters.
53    If so, returns the name of the counter parameter and the number of shells
54    """
55    iter_params = getIterParams(model)
56    param_name = ""
57    param_length = 0
58    if iter_params:
59        param_length = iter_params[0].length
60        param_name = iter_params[0].length_control
61        if param_name is None and '[' in iter_params[0].name:
62            param_name = iter_params[0].name[:iter_params[0].name.index('[')]
63    return (param_name, param_length)
64
65def createFixedChoiceComboBox(param, item_row):
66    """
67    Determines whether param is a fixed-choice parameter, modifies items in item_row appropriately and returns a combo
68    box containing the fixed choices. Returns None if param is not fixed-choice.
69   
70    item_row is a list of QStandardItem objects for insertion into the parameter table.
71    """
72
73    # Determine whether this is a fixed-choice parameter. There are lots of conditionals, simply because the
74    # implementation is not yet concrete; there are several possible indicators that the parameter is fixed-choice.
75    # TODO: (when the sasmodels implementation is concrete, clean this up)
76    choices = None
77    if isinstance(param.choices, (list, tuple)) and len(param.choices) > 0:
78        # The choices property is concrete in sasmodels, probably will use this
79        choices = param.choices
80    elif isinstance(param.units, (list, tuple)):
81        choices = [str(x) for x in param.units]
82
83    cbox = None
84    if choices is not None:
85        # Use combo box for input, if it is fixed-choice
86        cbox = AssociatedComboBox(item_row[1], idx_as_value=True)
87        cbox.addItems(choices)
88        item_row[2].setEditable(False)
89        item_row[3].setEditable(False)
90
91    return cbox
92
93def addParametersToModel(parameters, kernel_module, is2D, model=None, view=None):
94    """
95    Update local ModelModel with sasmodel parameters.
96    Actually appends to model, if model and view params are not None.
97    Always returns list of lists of QStandardItems.
98    """
99    multishell_parameters = getIterParams(parameters)
100    multishell_param_name, _ = getMultiplicity(parameters)
101
102    if is2D:
103        params = [p for p in parameters.kernel_parameters if p.type != 'magnetic']
104    else:
105        params = parameters.iq_parameters
106
107    rows = []
108    for param in params:
109        # don't include shell parameters
110        if param.name == multishell_param_name:
111            continue
112
113        # Modify parameter name from <param>[n] to <param>1
114        item_name = param.name
115        if param in multishell_parameters:
116            continue
117
118        item1 = QtGui.QStandardItem(item_name)
119        item1.setCheckable(True)
120        item1.setEditable(False)
121
122        # check for polydisp params
123        if param.polydisperse:
124            poly_item = QtGui.QStandardItem("Polydispersity")
125            poly_item.setEditable(False)
126            item1_1 = QtGui.QStandardItem("Distribution")
127            item1_1.setEditable(False)
128
129            # Find param in volume_params
130            for p in parameters.form_volume_parameters:
131                if p.name != param.name:
132                    continue
133                width = kernel_module.getParam(p.name+'.width')
134                ptype = kernel_module.getParam(p.name+'.type')
135                item1_2 = QtGui.QStandardItem(str(width))
136                item1_2.setEditable(False)
137                item1_3 = QtGui.QStandardItem()
138                item1_3.setEditable(False)
139                item1_4 = QtGui.QStandardItem()
140                item1_4.setEditable(False)
141                item1_5 = QtGui.QStandardItem(ptype)
142                item1_5.setEditable(False)
143                poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
144                break
145
146            # Add the polydisp item as a child
147            item1.appendRow([poly_item])
148
149        # Param values
150        item2 = QtGui.QStandardItem(str(param.default))
151        item3 = QtGui.QStandardItem(str(param.limits[0]))
152        item4 = QtGui.QStandardItem(str(param.limits[1]))
153        item5 = QtGui.QStandardItem(str(param.units))
154        item5.setEditable(False)
155
156        # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
157        row = [item1, item2, item3, item4, item5]
158        cbox = createFixedChoiceComboBox(param, row)
159
160        # Append to the model and use the combobox, if required
161        if None not in (model, view):
162            model.appendRow(row)
163            if cbox:
164                view.setIndexWidget(item2.index(), cbox)
165        rows.append(row)
166
167    return rows
168
169def addSimpleParametersToModel(parameters, is2D, parameters_original=None, model=None, view=None):
170    """
171    Update local ModelModel with sasmodel parameters (non-dispersed, non-magnetic)
172    Actually appends to model, if model and view params are not None.
173    Always returns list of lists of QStandardItems.
174
175    parameters_original: list of parameters before any tagging on their IDs, e.g. for product model (so that those are
176    the display names; see below)
177    """
178    if is2D:
179        params = [p for p in parameters.kernel_parameters if p.type != 'magnetic']
180    else:
181        params = parameters.iq_parameters
182
183    if parameters_original:
184        # 'parameters_original' contains the parameters as they are to be DISPLAYED, while 'parameters'
185        # contains the parameters as they were renamed; this is for handling name collisions in product model.
186        # The 'real name' of the parameter will be stored in the item's user data.
187        if is2D:
188            params_orig = [p for p in parameters_original.kernel_parameters if p.type != 'magnetic']
189        else:
190            params_orig = parameters_original.iq_parameters
191    else:
192        # no difference in names anyway
193        params_orig = params
194
195    rows = []
196    for param, param_orig in zip(params, params_orig):
197        # Create the top level, checkable item
198        item_name = param_orig.name
199        item1 = QtGui.QStandardItem(item_name)
200        item1.setData(param.name, QtCore.Qt.UserRole)
201        item1.setCheckable(True)
202        item1.setEditable(False)
203
204        # Param values
205        # TODO: add delegate for validation of cells
206        item2 = QtGui.QStandardItem(str(param.default))
207        item3 = QtGui.QStandardItem(str(param.limits[0]))
208        item4 = QtGui.QStandardItem(str(param.limits[1]))
209        item5 = QtGui.QStandardItem(str(param.units))
210        item5.setEditable(False)
211
212        # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
213        row = [item1, item2, item3, item4, item5]
214        cbox = createFixedChoiceComboBox(param, row)
215
216        # Append to the model and use the combobox, if required
217        if None not in (model, view):
218            model.appendRow(row)
219            if cbox:
220                view.setIndexWidget(item2.index(), cbox)
221        rows.append(row)
222
223    return rows
224
225def markParameterDisabled(model, row):
226    """Given the QModel row number, format to show it is not available for fitting"""
227
228    # If an error column is present, there are a total of 6 columns.
229    items = [model.item(row, c) for c in range(6)]
230
231    model.blockSignals(True)
232
233    for item in items:
234        if item is None:
235            continue
236        item.setEditable(False)
237        item.setCheckable(False)
238
239    item = items[0]
240
241    font = QtGui.QFont()
242    font.setItalic(True)
243    item.setFont(font)
244    item.setForeground(QtGui.QBrush(QtGui.QColor(100, 100, 100)))
245    item.setToolTip("This parameter cannot be fitted.")
246
247    model.blockSignals(False)
248
249def addCheckedListToModel(model, param_list):
250    """
251    Add a QItem to model. Makes the QItem checkable
252    """
253    assert isinstance(model, QtGui.QStandardItemModel)
254    item_list = [QtGui.QStandardItem(item) for item in param_list]
255    item_list[0].setCheckable(True)
256    model.appendRow(item_list)
257
258def addHeadingRowToModel(model, name):
259    """adds a non-interactive top-level row to the model"""
260    header_row = [QtGui.QStandardItem() for i in range(5)]
261    header_row[0].setText(name)
262
263    font = header_row[0].font()
264    font.setBold(True)
265    header_row[0].setFont(font)
266
267    for item in header_row:
268        item.setEditable(False)
269        item.setCheckable(False)
270        item.setSelectable(False)
271
272    model.appendRow(header_row)
273
274def addHeadersToModel(model):
275    """
276    Adds predefined headers to the model
277    """
278    for i, item in enumerate(model_header_captions):
279        model.setHeaderData(i, QtCore.Qt.Horizontal, item)
280
281    model.header_tooltips = copy.copy(model_header_tooltips)
282
283def addErrorHeadersToModel(model):
284    """
285    Adds predefined headers to the model
286    """
287    model_header_error_captions = copy.copy(model_header_captions)
288    model_header_error_captions.insert(2, header_error_caption)
289    for i, item in enumerate(model_header_error_captions):
290        model.setHeaderData(i, QtCore.Qt.Horizontal, item)
291
292    model_header_error_tooltips = copy.copy(model_header_tooltips)
293    model_header_error_tooltips.insert(2, error_tooltip)
294    model.header_tooltips = copy.copy(model_header_error_tooltips)
295
296def addPolyHeadersToModel(model):
297    """
298    Adds predefined headers to the model
299    """
300    for i, item in enumerate(poly_header_captions):
301        model.setHeaderData(i, QtCore.Qt.Horizontal, item)
302
303    model.header_tooltips = copy.copy(poly_header_tooltips)
304
305
306def addErrorPolyHeadersToModel(model):
307    """
308    Adds predefined headers to the model
309    """
310    poly_header_error_captions = copy.copy(poly_header_captions)
311    poly_header_error_captions.insert(2, header_error_caption)
312    for i, item in enumerate(poly_header_error_captions):
313        model.setHeaderData(i, QtCore.Qt.Horizontal, item)
314
315    poly_header_error_tooltips = copy.copy(poly_header_tooltips)
316    poly_header_error_tooltips.insert(2, error_tooltip)
317    model.header_tooltips = copy.copy(poly_header_error_tooltips)
318
319def addShellsToModel(parameters, model, index, row_num=None, view=None):
320    """
321    Find out multishell parameters and update the model with the requested number of them.
322    Inserts them after the row at row_num, if not None; otherwise, appends to end.
323    If view param is not None, supports fixed-choice params.
324    Returns a list of lists of QStandardItem objects.
325    """
326    multishell_parameters = getIterParams(parameters)
327
328    rows = []
329    for i in range(index):
330        for par in multishell_parameters:
331            # Create the name: <param>[<i>], e.g. "sld1" for parameter "sld[n]"
332            param_name = replaceShellName(par.name, i+1)
333            item1 = QtGui.QStandardItem(param_name)
334            item1.setCheckable(True)
335            # check for polydisp params
336            if par.polydisperse:
337                poly_item = QtGui.QStandardItem("Polydispersity")
338                item1_1 = QtGui.QStandardItem("Distribution")
339                # Find param in volume_params
340                for p in parameters.form_volume_parameters:
341                    if p.name != par.name:
342                        continue
343                    item1_2 = QtGui.QStandardItem(str(p.default))
344                    item1_3 = QtGui.QStandardItem(str(p.limits[0]))
345                    item1_4 = QtGui.QStandardItem(str(p.limits[1]))
346                    item1_5 = QtGui.QStandardItem(str(p.units))
347                    poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
348                    break
349                item1.appendRow([poly_item])
350
351            item2 = QtGui.QStandardItem(str(par.default))
352            item3 = QtGui.QStandardItem(str(par.limits[0]))
353            item4 = QtGui.QStandardItem(str(par.limits[1]))
354            item5 = QtGui.QStandardItem(str(par.units))
355            item5.setEditable(False)
356
357            # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
358            row = [item1, item2, item3, item4, item5]
359            cbox = createFixedChoiceComboBox(par, row)
360
361            # Always add to the model
362            if row_num is None:
363                model.appendRow(row)
364            else:
365                model.insertRow(row_num, row)
366                row_num += 1
367
368            # Apply combobox if required
369            if None not in (view, cbox):
370                view.setIndexWidget(item2.index(), cbox)
371
372            rows.append(row)
373
374    return rows
375
376def calculateChi2(reference_data, current_data):
377    """
378    Calculate Chi2 value between two sets of data
379    """
380    if reference_data is None or current_data is None:
381        return None
382    # WEIGHING INPUT
383    #from sas.sasgui.perspectives.fitting.utils import get_weight
384    #flag = self.get_weight_flag()
385    #weight = get_weight(data=self.data, is2d=self._is_2D(), flag=flag)
386    chisqr = None
387    if reference_data is None:
388        return chisqr
389
390    # temporary default values for index and weight
391    index = None
392    weight = None
393
394    # Get data: data I, theory I, and data dI in order
395    if isinstance(reference_data, Data2D):
396        if index is None:
397            index = numpy.ones(len(current_data.data), dtype=bool)
398        if weight is not None:
399            current_data.err_data = weight
400        # get rid of zero error points
401        index = index & (current_data.err_data != 0)
402        index = index & (numpy.isfinite(current_data.data))
403        fn = current_data.data[index]
404        gn = reference_data.data[index]
405        en = current_data.err_data[index]
406    else:
407        # 1 d theory from model_thread is only in the range of index
408        if index is None:
409            index = numpy.ones(len(current_data.y), dtype=bool)
410        if weight is not None:
411            current_data.dy = weight
412        if current_data.dy is None or current_data.dy == []:
413            dy = numpy.ones(len(current_data.y))
414        else:
415            ## Set consistently w/AbstractFitengine:
416            # But this should be corrected later.
417            dy = copy.deepcopy(current_data.dy)
418            dy[dy == 0] = 1
419        fn = current_data.y[index]
420        gn = reference_data.y
421        en = dy[index]
422    # Calculate the residual
423    try:
424        res = (fn - gn) / en
425    except ValueError:
426        #print "Chi2 calculations: Unmatched lengths %s, %s, %s" % (len(fn), len(gn), len(en))
427        return None
428
429    residuals = res[numpy.isfinite(res)]
430    chisqr = numpy.average(residuals * residuals)
431
432    return chisqr
433
434def residualsData1D(reference_data, current_data):
435    """
436    Calculate the residuals for difference of two Data1D sets
437    """
438    # temporary default values for index and weight
439    index = None
440    weight = None
441
442    # 1d theory from model_thread is only in the range of index
443    if current_data.dy is None or current_data.dy == []:
444        dy = numpy.ones(len(current_data.y))
445    else:
446        dy = weight if weight is not None else numpy.ones(len(current_data.y))
447        dy[dy == 0] = 1
448    fn = current_data.y[index][0]
449    gn = reference_data.y
450    en = dy[index][0]
451
452    # x values
453    x_current = current_data.x
454    x_reference = reference_data.x
455
456    # build residuals
457    residuals = Data1D()
458    if len(fn) == len(gn):
459        y = (fn - gn)/en
460        residuals.y = -y
461    elif len(fn) > len(gn):
462        residuals.y = (fn - gn[1:len(fn)])/en
463    else:
464        try:
465            y = numpy.zeros(len(current_data.y))
466            begin = 0
467            for i, x_value in enumerate(x_reference):
468                if x_value in x_current:
469                    begin = i
470                    break
471            end = len(x_reference)
472            endl = 0
473            for i, x_value in enumerate(list(x_reference)[::-1]):
474                if x_value in x_current:
475                    endl = i
476                    break
477            # make sure we have correct lengths
478            assert len(x_current) == len(x_reference[begin:end-endl])
479
480            y = (fn - gn[begin:end-endl])/en
481            residuals.y = y
482        except ValueError:
483            # value errors may show up every once in a while for malformed columns,
484            # just reuse what's there already
485            pass
486
487    residuals.x = current_data.x[index][0]
488    residuals.dy = numpy.ones(len(residuals.y))
489    residuals.dx = None
490    residuals.dxl = None
491    residuals.dxw = None
492    residuals.ytransform = 'y'
493    # For latter scale changes
494    residuals.xaxis('\\rm{Q} ', 'A^{-1}')
495    residuals.yaxis('\\rm{Residuals} ', 'normalized')
496
497    return residuals
498
499def residualsData2D(reference_data, current_data):
500    """
501    Calculate the residuals for difference of two Data2D sets
502    """
503    # temporary default values for index and weight
504    # index = None
505    weight = None
506
507    # build residuals
508    residuals = Data2D()
509    # Not for trunk the line below, instead use the line above
510    current_data.clone_without_data(len(current_data.data), residuals)
511    residuals.data = None
512    fn = current_data.data
513    gn = reference_data.data
514    en = current_data.err_data if weight is None else weight
515    residuals.data = (fn - gn) / en
516    residuals.qx_data = current_data.qx_data
517    residuals.qy_data = current_data.qy_data
518    residuals.q_data = current_data.q_data
519    residuals.err_data = numpy.ones(len(residuals.data))
520    residuals.xmin = min(residuals.qx_data)
521    residuals.xmax = max(residuals.qx_data)
522    residuals.ymin = min(residuals.qy_data)
523    residuals.ymax = max(residuals.qy_data)
524    residuals.q_data = current_data.q_data
525    residuals.mask = current_data.mask
526    residuals.scale = 'linear'
527    # check the lengths
528    if len(residuals.data) != len(residuals.q_data):
529        return None
530    return residuals
531
532def plotResiduals(reference_data, current_data):
533    """
534    Create Data1D/Data2D with residuals, ready for plotting
535    """
536    data_copy = copy.deepcopy(current_data)
537    # Get data: data I, theory I, and data dI in order
538    method_name = current_data.__class__.__name__
539    residuals_dict = {"Data1D": residualsData1D,
540                      "Data2D": residualsData2D}
541
542    residuals = residuals_dict[method_name](reference_data, data_copy)
543
544    theory_name = str(current_data.name.split()[0])
545    res_name = reference_data.filename if reference_data.filename else reference_data.name
546    residuals.name = "Residuals for " + str(theory_name) + "[" + res_name + "]"
547    residuals.title = residuals.name
548    residuals.ytransform = 'y'
549
550    # when 2 data have the same id override the 1 st plotted
551    # include the last part if keeping charts for separate models is required
552    residuals.id = "res" + str(reference_data.id) # + str(theory_name)
553    # group_id specify on which panel to plot this data
554    group_id = reference_data.group_id
555    residuals.group_id = "res" + str(group_id)
556
557    # Symbol
558    residuals.symbol = 0
559    residuals.hide_error = False
560
561    return residuals
562
563def binary_encode(i, digits):
564    return [i >> d & 1 for d in range(digits)]
565
566def getWeight(data, is2d, flag=None):
567    """
568    Received flag and compute error on data.
569    :param flag: flag to transform error of data.
570    """
571    weight = None
572    if data is None:
573        return []
574    if is2d:
575        if not hasattr(data, 'err_data'):
576            return []
577        dy_data = data.err_data
578        data = data.data
579    else:
580        if not hasattr(data, 'dy'):
581            return []
582        dy_data = data.dy
583        data = data.y
584
585    if flag == 0:
586        weight = numpy.ones_like(data)
587    elif flag == 1:
588        weight = dy_data
589    elif flag == 2:
590        weight = numpy.sqrt(numpy.abs(data))
591    elif flag == 3:
592        weight = numpy.abs(data)
593    return weight
594
595def updateKernelWithResults(kernel, results):
596    """
597    Takes model kernel and applies results dict to its parameters,
598    returning the modified (deep) copy of the kernel.
599    """
600    assert isinstance(results, dict)
601    local_kernel = copy.deepcopy(kernel)
602
603    for parameter in results.keys():
604        # Update the parameter value - note: this supports +/-inf as well
605        local_kernel.setParam(parameter, results[parameter][0])
606
607    return local_kernel
608
609
610def getStandardParam(model=None):
611    """
612    Returns a list with standard parameters for the current model
613    """
614    param = []
615    num_rows = model.rowCount()
616    if num_rows < 1:
617        return None
618
619    for row in range(num_rows):
620        param_name = model.item(row, 0).text()
621        checkbox_state = model.item(row, 0).checkState() == QtCore.Qt.Checked
622        value = model.item(row, 1).text()
623        column_shift = 0
624        if model.columnCount() == 5: # no error column
625            error_state = False
626            error_value = 0.0
627        else:
628            error_state = True
629            error_value = model.item(row, 2).text()
630            column_shift = 1
631        min_state = True
632        max_state = True
633        min_value = model.item(row, 2+column_shift).text()
634        max_value = model.item(row, 3+column_shift).text()
635        unit = ""
636        if model.item(row, 4+column_shift) is not None:
637            unit = model.item(row, 4+column_shift).text()
638
639        param.append([checkbox_state, param_name, value, "",
640                        [error_state, error_value],
641                        [min_state, min_value],
642                        [max_state, max_value], unit])
643
644    return param
645
646def getOrientationParam(kernel_module=None):
647    """
648    Get the dictionary with orientation parameters
649    """
650    param = []
651    if kernel_module is None:
652        return None
653    for param_name in list(kernel_module.params.keys()):
654        name = param_name
655        value = kernel_module.params[param_name]
656        min_state = True
657        max_state = True
658        error_state = False
659        error_value = 0.0
660        checkbox_state = True #??
661        details = kernel_module.details[param_name] #[unit, mix, max]
662        param.append([checkbox_state, name, value, "",
663                     [error_state, error_value],
664                     [min_state, details[1]],
665                     [max_state, details[2]], details[0]])
666
667    return param
668
669def formatParameters(parameters):
670    """
671    Prepare the parameter string in the standard SasView layout
672    """
673    assert parameters is not None
674    assert isinstance(parameters, list)
675    output_string = "sasview_parameter_values:"
676    for parameter in parameters:
677        output_string += ",".join([p for p in parameter if p is not None])
678        output_string += ":"
679    return output_string
680
681def formatParametersExcel(parameters):
682    """
683    Prepare the parameter string in the Excel format (tab delimited)
684    """
685    assert parameters is not None
686    assert isinstance(parameters, list)
687    crlf = chr(13) + chr(10)
688    tab = chr(9)
689
690    output_string = ""
691    # names
692    names = ""
693    values = ""
694    for parameter in parameters:
695        names += parameter[0]+tab
696        # Add the error column if fitted
697        if parameter[1] == "True" and parameter[3] is not None:
698            names += parameter[0]+"_err"+tab
699
700        values += parameter[2]+tab
701        if parameter[1] == "True" and parameter[3] is not None:
702            values += parameter[3]+tab
703        # add .npts and .nsigmas when necessary
704        if parameter[0][-6:] == ".width":
705            names += parameter[0].replace('.width', '.nsigmas') + tab
706            names += parameter[0].replace('.width', '.npts') + tab
707            values += parameter[5] + tab + parameter[4] + tab
708
709    output_string = names + crlf + values
710    return output_string
711
712def formatParametersLatex(parameters):
713    """
714    Prepare the parameter string in latex
715    """
716    assert parameters is not None
717    assert isinstance(parameters, list)
718    output_string = r'\begin{table}'
719    output_string += r'\begin{tabular}[h]'
720
721    crlf = chr(13) + chr(10)
722    output_string += '{|'
723    output_string += 'l|l|'*len(parameters)
724    output_string += r'}\hline'
725    output_string += crlf
726
727    for index, parameter in enumerate(parameters):
728        name = parameter[0] # Parameter name
729        output_string += name.replace('_', r'\_')  # Escape underscores
730        # Add the error column if fitted
731        if parameter[1] == "True" and parameter[3] is not None:
732            output_string += ' & '
733            output_string += parameter[0]+r'\_err'
734
735        if index < len(parameters) - 1:
736            output_string += ' & '
737
738        # add .npts and .nsigmas when necessary
739        if parameter[0][-6:] == ".width":
740            output_string += parameter[0].replace('.width', '.nsigmas') + ' & '
741            output_string += parameter[0].replace('.width', '.npts')
742
743            if index < len(parameters) - 1:
744                output_string += ' & '
745
746    output_string += r'\\ \hline'
747    output_string += crlf
748
749    # Construct row of values and errors
750    for index, parameter in enumerate(parameters):
751        output_string += parameter[2]
752        if parameter[1] == "True" and parameter[3] is not None:
753            output_string += ' & '
754            output_string += parameter[3]
755
756        if index < len(parameters) - 1:
757            output_string += ' & '
758
759        # add .npts and .nsigmas when necessary
760        if parameter[0][-6:] == ".width":
761            output_string += parameter[5] + ' & '
762            output_string += parameter[4]
763
764            if index < len(parameters) - 1:
765                output_string += ' & '
766
767    output_string += r'\\ \hline'
768    output_string += crlf
769    output_string += r'\end{tabular}'
770    output_string += r'\end{table}'
771
772    return output_string
Note: See TracBrowser for help on using the repository browser.