configuration.py 10.2 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
3
# Copyright (C) 2012, 2013 Centre de données Astrophysiques de Marseille
# Licensed under the CeCILL-v2 licence - see Licence_CeCILL_V2-en.txt
Yannick Roehlly's avatar
Yannick Roehlly committed
4
# Author: Yannick Roehlly
5
6
7
8

import configobj
import pkg_resources
import pkgutil
9
import collections
10
import multiprocessing as mp
11
import numpy as np
12
from glob import glob # To allow the use of glob() in "eval..."
13
14
from textwrap import wrap
from ..data import Database
15
from ..utils import read_table
16
17
from .. import creation_modules
from .. import analysis_modules
18
19
20
21
22


def list_modules(package_name):
    """Lists the modules available in a package

Yannick Roehlly's avatar
Yannick Roehlly committed
23
    Parameters
24
    ----------
25
    package_name: string
26
        Name of the package (e.g. pcigale.creation_modules).
27
28
29

    Returns
    -------
30
    module_name: array of strings
31
32
33
34
35
36
37
38
39
        List of the available modules.

    """
    directory = pkg_resources.resource_filename(package_name, '')
    module_names = [name for _, name, _ in pkgutil.iter_modules([directory])]

    return module_names


Yannick Roehlly's avatar
Yannick Roehlly committed
40
41
42
43
44
45
def evaluate_description(description):
    """Evaluate a description from the config file as a list.

    The description is read from the config file by configobj that transforms
    coma separated value in a list. From this description, this function try
    to evaluate the desired list of values:
46
47
48
    - If the description is a string beginning with 'eval ', then its content
      (without 'eval ') is evaluated as Python code and its result returned.
      An array is expected.
49
50
51
    - If the description is a string beginning by 'range', the start, step and
      stop values are then expected and the range is evaluated (stop included
      if reached.
Yannick Roehlly's avatar
Yannick Roehlly committed
52
53
    - Then the function tries to evaluate the description as a Numpy array of
      float and returns the mere list if this fails.
54

Yannick Roehlly's avatar
Yannick Roehlly committed
55
    Parameters
56
    ----------
57
    description: string or list
58
59
60
61
        The description to be evaluated.

    Returns
    -------
62
     results: list
Yannick Roehlly's avatar
Yannick Roehlly committed
63
        The evaluated list of values.
64
65

    """
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    results = description
    if type(description) == str:
        if description.startswith('eval '):
            results = eval(description[4:])
            # If the evaluation lead to a single value, we put it in a list.
            if not isinstance(results, collections.Iterable):
                results = [results]
        elif description.startswith('range '):
            start, stop, step = [float(item) for item
                                 in description[5:].split()]
            results = np.arange(start, stop+step, step)
        else:
            # We need to return a list to combine the list of possible values
            # for each parameter.
80
            results = [results]
81
82
83
84
85
86
87

    # We prefer to evaluate the parameter as a numpy array of floats if
    # possible.
    try:
        results = np.array(results, float)
    except ValueError:
        pass
88

Yannick Roehlly's avatar
Yannick Roehlly committed
89
    return results
90
91
92
93
94
95
96
97
98


class Configuration(object):
    """This class manages the configuration of pcigale.
    """

    def __init__(self, filename="pcigale.ini"):
        """Initialise a pcigale configuration.

Yannick Roehlly's avatar
Yannick Roehlly committed
99
        Parameters
100
        ----------
101
        filename: string
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
            Name of the configuration file (pcigale.conf by default).

        """
        self.config = configobj.ConfigObj(filename,
                                          write_empty_values=True,
                                          indent_type='  ')

    def create_blank_conf(self):
        """Create the initial configuration file

        Write the initial pcigale configuration file where the user can state
        which data file to use, which modules to use for the SED creation, as
        well as the method selected for statistical analysis.

        """

        self.config['data_file'] = ""
        self.config.comments['data_file'] = wrap(
            "File containing the observation data to be fitted. Each flux "
            "column must have the name of the corresponding filter, the "
            "error columns are suffixed with '_err'. The values must be "
            "in mJy.")

125
        self.config['creation_modules'] = []
126
        self.config.comments['creation_modules'] = [""] + wrap(
127
            "Order of the modules use for SED creation. Available modules:"
128
            "SFH: sfh2exp, sfhdelayed, sfhfromfile ; "
129
130
131
            "SSP: bc03, m2005 ; "
            "Nebular: nebular ; "
            "Attenuation: dustatt_calzleit, dustatt_powerlaw ; "
132
            "Dust model: casey2012, dh2002, dl2007, dl2014 ; "
133
            "AGN: dale2014, fritz2006 ; "
134
            "redshift: redshifting (mandatory!).")
135
136
137
138

        self.config['analysis_method'] = ""
        self.config.comments['analysis_method'] = [""] + wrap(
            "Method used for statistical analysis. Available methods: "
139
            "pdf_analysis, savefluxes.")
140

141
142
143
144
145
        self.config['cores'] = ""
        self.config.comments['cores'] = [""] + wrap(
            "Number of CPU cores available. This computer has {} cores."
            .format(mp.cpu_count()))

146
147
148
149
150
151
152
153
154
155
156
157
        self.config.write()

    def generate_conf(self):
        """Generate the full configuration file

        Reads the user entries in the initial configuration file and add the
        configuration options of all selected modules as well as the filter
        selection based on the filters identified in the data table file.

        """

        # Getting the list of the filters available in pcigale database
158
159
        with Database() as base:
            filter_list = base.get_filter_list()[0]
160

161
        obs_table = read_table(self.config['data_file'])
162
163
164
165
166
167
168
169

        # Check that the id and redshift columns are present in the input file
        if 'id' not in obs_table.columns:
            raise Exception("Column id not present in input file")
        if 'redshift' not in obs_table.columns:
            raise Exception("Column redshift not present in input file")

        # Finding the known filters in the data table
170
171
172
173
174
175
176
177
178
        column_list = []
        for column in obs_table.columns:
            filter_name = column[:-4] if column.endswith('_err') else column
            if filter_name in filter_list:
                column_list.append(column)

        # Check that we don't have an error column without the associated flux
        for column in column_list:
            if column.endswith('_err') and (column[:-4] not in column_list):
179
                raise Exception("The observation table as a {} column "
Médéric Boquien's avatar
Médéric Boquien committed
180
181
                                "but no {} column.".format(column,
                                                           column[:-4]))
182
183
184
185
186
187
188

        self.config['column_list'] = column_list
        self.config.comments['column_list'] = [""] + wrap(
            "List of the columns in the observation data file to use for "
            "the fitting.")

        # SED creation modules configurations. For each module, we generate
Yannick Roehlly's avatar
Yannick Roehlly committed
189
        # the configuration section from its parameter list.
190
191
192
193
        self.config['sed_creation_modules'] = {}
        self.config.comments['sed_creation_modules'] = ["", ""] + wrap(
            "Configuration of the SED creation modules.")

194
        for module_name in self.config['creation_modules']:
195
196
197
            self.config["sed_creation_modules"][module_name] = {}
            sub_config = self.config["sed_creation_modules"][module_name]

198
            for name, (typ, description, default) in \
199
200
201
                    creation_modules.get_module(
                        module_name,
                        blank=True).parameter_list.items():
202
203
204
205
206
207
                if default is None:
                    default = ''
                sub_config[name] = default
                sub_config.comments[name] = wrap(description)

            self.config['sed_creation_modules'].comments[module_name] = [
208
                creation_modules.get_module(module_name, blank=True).comments]
209
210
211
212
213
214

        # Configuration for the analysis method
        self.config['analysis_configuration'] = {}
        self.config.comments['analysis_configuration'] = ["", ""] + wrap(
            "Configuration of the statistical analysis method.")
        module_name = self.config['analysis_method']
215
        for name, (typ, desc, default) in \
216
                analysis_modules.get_module(module_name).parameter_list.items():
217
218
219
220
221
222
            if default is None:
                default = ''
            self.config['analysis_configuration'][name] = default
            self.config['analysis_configuration'].comments[name] = wrap(desc)

        self.config.write()
Yannick Roehlly's avatar
Yannick Roehlly committed
223

224
225
    @property
    def configuration(self):
Yannick Roehlly's avatar
Yannick Roehlly committed
226
        """Returns a dictionary for the session configuration.
Yannick Roehlly's avatar
Yannick Roehlly committed
227
228
229

        Returns
        -------
230
        configuration['data_file']: string
Yannick Roehlly's avatar
Yannick Roehlly committed
231
            File containing the observations to fit.
232
        configuration['column_list']: list of strings
Yannick Roehlly's avatar
Yannick Roehlly committed
233
            List of the columns of data_file to use in the fitting.
234
        configuration['creation_modules']: list of strings
Yannick Roehlly's avatar
Yannick Roehlly committed
235
            List of the modules (in the right order) used to create the SEDs.
236
        configuration['creation_modules_params']: list of dictionaries
Yannick Roehlly's avatar
Yannick Roehlly committed
237
238
            Configuration parameters for each module. To each parameter, the
            dictionary associates a list of possible values (possibly only
Yannick Roehlly's avatar
Yannick Roehlly committed
239
            one).
240
        configuration['analysis_method']: string
Yannick Roehlly's avatar
Yannick Roehlly committed
241
            Statistical analysis module used to fit the data.
242
        configuration['analysis_method_params']: dictionary
Yannick Roehlly's avatar
Yannick Roehlly committed
243
            Parameters for the statistical analysis module. To each parameter
Yannick Roehlly's avatar
Yannick Roehlly committed
244
245
246
247
            is associated a list of possible values.
        """
        configuration = {}

248
        for section in ['data_file', 'column_list', 'creation_modules',
249
                        'analysis_method']:
Yannick Roehlly's avatar
Yannick Roehlly committed
250
            configuration[section] = self.config[section]
251
        configuration['cores'] = int(self.config['cores'])
Yannick Roehlly's avatar
Yannick Roehlly committed
252

Yannick Roehlly's avatar
Yannick Roehlly committed
253
        # Parsing the SED modules parameters
254
255
        configuration['creation_modules_params'] = []
        for module in self.config['creation_modules']:
256
            module_params = collections.OrderedDict()
Yannick Roehlly's avatar
Yannick Roehlly committed
257
258
            for key, value in \
                    self.config['sed_creation_modules'][module].items():
Yannick Roehlly's avatar
Yannick Roehlly committed
259
                module_params[key] = evaluate_description(value)
260
            configuration['creation_modules_params'].append(module_params)
261
262

        # Analysis method parameters
263
264
        configuration['analysis_method_params'] = \
            self.config['analysis_configuration']
Yannick Roehlly's avatar
Yannick Roehlly committed
265
266

        return configuration