configuration.py 10.6 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
            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(
120 121 122 123 124 125
            "File containing the input data. The columns are 'id' (name of the"
            " object), 'redshift' (if 0 the distance is assumed to be 10 pc), "
            "the filter names for the fluxes, and the filter names with the "
            "'_err' suffix for the uncertainties. The fluxes and the "
            "uncertainties must be in mJy. This file is optional to generate "
            "the configuration file, in particular for the savefluxes module.")
126

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

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

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

148 149 150 151 152 153 154 155 156 157 158 159
        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
160 161
        with Database() as base:
            filter_list = base.get_filter_list()[0]
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
        if self.config['data_file'] != '':
            obs_table = read_table(self.config['data_file'])

            # 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
            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):
                    raise Exception("The observation table as a {} column "
                                    "but no {} column.".format(column,
                                                            column[:-4]))

            self.config['column_list'] = column_list
        else:
            self.config['column_list'] = ''
191 192 193 194 195
        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
196
        # the configuration section from its parameter list.
197 198 199 200
        self.config['sed_creation_modules'] = {}
        self.config.comments['sed_creation_modules'] = ["", ""] + wrap(
            "Configuration of the SED creation modules.")

201
        for module_name in self.config['creation_modules']:
202 203 204
            self.config["sed_creation_modules"][module_name] = {}
            sub_config = self.config["sed_creation_modules"][module_name]

205
            for name, (typ, description, default) in \
206 207 208
                    creation_modules.get_module(
                        module_name,
                        blank=True).parameter_list.items():
209 210 211 212 213 214
                if default is None:
                    default = ''
                sub_config[name] = default
                sub_config.comments[name] = wrap(description)

            self.config['sed_creation_modules'].comments[module_name] = [
215
                creation_modules.get_module(module_name, blank=True).comments]
216 217 218 219 220 221

        # 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']
222
        for name, (typ, desc, default) in \
223
                analysis_modules.get_module(module_name).parameter_list.items():
224 225 226 227 228 229
            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
230

231 232
    @property
    def configuration(self):
Yannick Roehlly's avatar
Yannick Roehlly committed
233
        """Returns a dictionary for the session configuration.
Yannick Roehlly's avatar
Yannick Roehlly committed
234 235 236

        Returns
        -------
237
        configuration['data_file']: string
Yannick Roehlly's avatar
Yannick Roehlly committed
238
            File containing the observations to fit.
239
        configuration['column_list']: list of strings
Yannick Roehlly's avatar
Yannick Roehlly committed
240
            List of the columns of data_file to use in the fitting.
241
        configuration['creation_modules']: list of strings
Yannick Roehlly's avatar
Yannick Roehlly committed
242
            List of the modules (in the right order) used to create the SEDs.
243
        configuration['creation_modules_params']: list of dictionaries
Yannick Roehlly's avatar
Yannick Roehlly committed
244 245
            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
246
            one).
247
        configuration['analysis_method']: string
Yannick Roehlly's avatar
Yannick Roehlly committed
248
            Statistical analysis module used to fit the data.
249
        configuration['analysis_method_params']: dictionary
Yannick Roehlly's avatar
Yannick Roehlly committed
250
            Parameters for the statistical analysis module. To each parameter
Yannick Roehlly's avatar
Yannick Roehlly committed
251 252 253 254
            is associated a list of possible values.
        """
        configuration = {}

255
        for section in ['data_file', 'column_list', 'creation_modules',
256
                        'analysis_method']:
Yannick Roehlly's avatar
Yannick Roehlly committed
257
            configuration[section] = self.config[section]
258
        configuration['cores'] = int(self.config['cores'])
Yannick Roehlly's avatar
Yannick Roehlly committed
259

Yannick Roehlly's avatar
Yannick Roehlly committed
260
        # Parsing the SED modules parameters
261 262
        configuration['creation_modules_params'] = []
        for module in self.config['creation_modules']:
263
            module_params = collections.OrderedDict()
Yannick Roehlly's avatar
Yannick Roehlly committed
264 265
            for key, value in \
                    self.config['sed_creation_modules'][module].items():
Yannick Roehlly's avatar
Yannick Roehlly committed
266
                module_params[key] = evaluate_description(value)
267
            configuration['creation_modules_params'].append(module_params)
268 269

        # Analysis method parameters
270 271
        configuration['analysis_method_params'] = \
            self.config['analysis_configuration']
Yannick Roehlly's avatar
Yannick Roehlly committed
272 273

        return configuration