__init__.py 32.8 KB
Newer Older
Yannick Roehlly's avatar
Yannick Roehlly committed
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
Yannick Roehlly's avatar
Yannick Roehlly committed
5

6
"""
Yannick Roehlly's avatar
Yannick Roehlly committed
7 8 9 10 11 12 13 14 15 16 17 18
This is the database where we store some data used by pcigale:
 - the information relative to the filters
 - the single stellar populations as defined in Marason (2005)
 - the infra-red templates from Dale and Helou (2002)

The classes for these various objects are described in pcigale.data
sub-packages. The corresponding underscored classes here are used by the
SqlAlchemy ORM to store the data in a unique SQLite3 database.

"""

import pkg_resources
19
from sqlalchemy import create_engine, exc, Column, String,  Float, PickleType
Yannick Roehlly's avatar
Yannick Roehlly committed
20
from sqlalchemy.ext.declarative import declarative_base
21 22
from sqlalchemy.orm import class_mapper, sessionmaker
import numpy as np
23

Yannick Roehlly's avatar
Yannick Roehlly committed
24
from .filters import Filter
25 26 27 28
from .m2005 import M2005
from .bc03 import BC03
from .dale2014 import Dale2014
from .dl2007 import DL2007
29
from .dl2014 import DL2014
30 31
from .fritz2006 import Fritz2006
from .nebular_continuum import NebularContinuum
32
from .nebular_lines import NebularLines
33
from .schreiber2016 import Schreiber2016
34
from .themis import THEMIS
Yannick Roehlly's avatar
Yannick Roehlly committed
35 36 37 38 39 40 41 42

DATABASE_FILE = pkg_resources.resource_filename(__name__, 'data.db')

ENGINE = create_engine('sqlite:///' + DATABASE_FILE, echo=False)
BASE = declarative_base()
SESSION = sessionmaker(bind=ENGINE)


43 44 45 46 47 48 49 50 51 52 53 54 55 56
class DatabaseLookupError(Exception):
    """
    A custom exception raised when a search in the database does not find a
    result.
    """


class DatabaseInsertError(Exception):
    """
    A custom exception raised when one tries to insert in the database
    something that is already in it.
    """


Yannick Roehlly's avatar
Yannick Roehlly committed
57 58 59 60 61 62 63 64 65
class _Filter(BASE):
    """ Storage for filters
    """

    __tablename__ = 'filters'

    name = Column(String, primary_key=True)
    description = Column(String)
    trans_table = Column(PickleType)
66
    pivot_wavelength = Column(Float)
Yannick Roehlly's avatar
Yannick Roehlly committed
67 68 69 70 71

    def __init__(self, f):
        self.name = f.name
        self.description = f.description
        self.trans_table = f.trans_table
72
        self.pivot_wavelength = f.pivot_wavelength
Yannick Roehlly's avatar
Yannick Roehlly committed
73 74


75
class _M2005(BASE):
Yannick Roehlly's avatar
Yannick Roehlly committed
76 77 78 79 80 81 82
    """Storage for Maraston 2005 SSP
    """

    __tablename__ = 'maraston2005'

    imf = Column(String, primary_key=True)
    metallicity = Column(Float, primary_key=True)
Yannick Roehlly's avatar
Yannick Roehlly committed
83
    time_grid = Column(PickleType)
Yannick Roehlly's avatar
Yannick Roehlly committed
84
    wavelength_grid = Column(PickleType)
85
    info_table = Column(PickleType)
Yannick Roehlly's avatar
Yannick Roehlly committed
86 87 88 89 90
    spec_table = Column(PickleType)

    def __init__(self, ssp):
        self.imf = ssp.imf
        self.metallicity = ssp.metallicity
Yannick Roehlly's avatar
Yannick Roehlly committed
91
        self.time_grid = ssp.time_grid
Yannick Roehlly's avatar
Yannick Roehlly committed
92
        self.wavelength_grid = ssp.wavelength_grid
93
        self.info_table = ssp.info_table
Yannick Roehlly's avatar
Yannick Roehlly committed
94 95 96
        self.spec_table = ssp.spec_table


97
class _BC03(BASE):
98 99 100 101 102 103 104 105 106
    """Storage for Bruzual and Charlot 2003 SSP
    """

    __tablename__ = "bc03"

    imf = Column(String, primary_key=True)
    metallicity = Column(Float, primary_key=True)
    time_grid = Column(PickleType)
    wavelength_grid = Column(PickleType)
107 108
    info_table = Column(PickleType)
    spec_table = Column(PickleType)
109 110 111 112 113 114

    def __init__(self, ssp):
        self.imf = ssp.imf
        self.metallicity = ssp.metallicity
        self.time_grid = ssp.time_grid
        self.wavelength_grid = ssp.wavelength_grid
115 116
        self.info_table = ssp.info_table
        self.spec_table = ssp.spec_table
117 118


119
class _Dale2014(BASE):
120 121 122 123 124 125 126 127 128 129 130 131 132 133
    """Storage for Dale et al (2014) infra-red templates
    """

    __tablename__ = 'dale2014_templates'
    fracAGN = Column(Float, primary_key=True)
    alpha = Column(String, primary_key=True)
    wave = Column(PickleType)
    lumin = Column(PickleType)

    def __init__(self, iragn):
        self.fracAGN = iragn.fracAGN
        self.alpha = iragn.alpha
        self.wave = iragn.wave
        self.lumin = iragn.lumin
Yannick Roehlly's avatar
Yannick Roehlly committed
134

135

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
class _DL2007(BASE):
    """Storage for Draine and Li (2007) IR models
    """

    __tablename__ = 'DL2007_models'
    qpah = Column(Float, primary_key=True)
    umin = Column(Float, primary_key=True)
    umax = Column(Float, primary_key=True)
    wave = Column(PickleType)
    lumin = Column(PickleType)

    def __init__(self, model):
        self.qpah = model.qpah
        self.umin = model.umin
        self.umax = model.umax
        self.wave = model.wave
        self.lumin = model.lumin


155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
class _DL2014(BASE):
    """Storage for the updated Draine and Li (2007) IR models
    """

    __tablename__ = 'DL2014_models'
    qpah = Column(Float, primary_key=True)
    umin = Column(Float, primary_key=True)
    umax = Column(Float, primary_key=True)
    alpha = Column(Float, primary_key=True)
    wave = Column(PickleType)
    lumin = Column(PickleType)

    def __init__(self, model):
        self.qpah = model.qpah
        self.umin = model.umin
        self.umax = model.umax
        self.alpha = model.alpha
        self.wave = model.wave
        self.lumin = model.lumin


176
class _Fritz2006(BASE):
177 178 179
    """Storage for Fritz et al. (2006) models
    """

180 181 182 183 184 185 186
    __tablename__ = 'fritz2006'
    r_ratio = Column(Float, primary_key=True)
    tau = Column(Float, primary_key=True)
    beta = Column(Float, primary_key=True)
    gamma = Column(Float, primary_key=True)
    opening_angle = Column(Float, primary_key=True)
    psy = Column(Float, primary_key=True)
187
    wave = Column(PickleType)
188 189
    lumin_therm = Column(PickleType)
    lumin_scatt = Column(PickleType)
190
    lumin_agn = Column(PickleType)
191 192 193 194 195 196

    def __init__(self, agn):
        self.r_ratio = agn.r_ratio
        self.tau = agn.tau
        self.beta = agn.beta
        self.gamma = agn.gamma
197
        self.opening_angle = agn.opening_angle
198 199
        self.psy = agn.psy
        self.wave = agn.wave
200 201 202
        self.lumin_therm = agn.lumin_therm
        self.lumin_scatt = agn.lumin_scatt
        self.lumin_agn = agn.lumin_agn
203

204

205
class _NebularLines(BASE):
206 207
    """Storage for line templates
    """
208 209

    __tablename__ = 'nebular_lines'
210 211
    metallicity = Column(Float, primary_key=True)
    logU = Column(Float, primary_key=True)
212
    name = Column(PickleType)
213 214
    wave = Column(PickleType)
    ratio = Column(PickleType)
215 216 217 218

    def __init__(self, nebular_lines):
        self.metallicity = nebular_lines.metallicity
        self.logU = nebular_lines.logU
219
        self.name = nebular_lines.name
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
        self.wave = nebular_lines.wave
        self.ratio = nebular_lines.ratio


class _NebularContinuum(BASE):
    """Storage for nebular continuum templates
    """

    __tablename__ = 'nebular_continuum'
    metallicity = Column(Float, primary_key=True)
    logU = Column(Float, primary_key=True)
    wave = Column(PickleType)
    lumin = Column(PickleType)

    def __init__(self, nebular_continuum):
        self.metallicity = nebular_continuum.metallicity
        self.logU = nebular_continuum.logU
        self.wave = nebular_continuum.wave
        self.lumin = nebular_continuum.lumin
239

240

241 242 243
class _Schreiber2016(BASE):
    """Storage for Schreiber et al (2016) infra-red templates
        """
244

245 246 247 248 249
    __tablename__ = 'schreiber2016_templates'
    type = Column(Float, primary_key=True)
    tdust = Column(String, primary_key=True)
    wave = Column(PickleType)
    lumin = Column(PickleType)
250

251 252 253 254 255
    def __init__(self, ir):
        self.type = ir.type
        self.tdust = ir.tdust
        self.wave = ir.wave
        self.lumin = ir.lumin
256

257

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
class _THEMIS(BASE):
    """Storage for the Jones et al (2017) IR models
    """

    __tablename__ = 'THEMIS_models'
    qhac = Column(Float, primary_key=True)
    umin = Column(Float, primary_key=True)
    umax = Column(Float, primary_key=True)
    alpha = Column(Float, primary_key=True)
    wave = Column(PickleType)
    lumin = Column(PickleType)

    def __init__(self, model):
        self.qhac = model.qhac
        self.umin = model.umin
        self.umax = model.umax
        self.alpha = model.alpha
        self.wave = model.wave
        self.lumin = model.lumin


Yannick Roehlly's avatar
Yannick Roehlly committed
279 280 281 282 283 284 285
class Database(object):
    """Object giving access to pcigale database."""

    def __init__(self, writable=False):
        """
        Create a collection giving access to access the pcigale database.

Yannick Roehlly's avatar
Yannick Roehlly committed
286
        Parameters
Yannick Roehlly's avatar
Yannick Roehlly committed
287
        ----------
288
        writable: boolean
Yannick Roehlly's avatar
Yannick Roehlly committed
289 290 291 292 293 294 295
            If True the user will be able to write new data in the database
            (but he/she must have a writable access to the sqlite file). By
            default, False.
        """
        self.session = SESSION()
        self.is_writable = writable

296 297 298 299 300 301
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

Yannick Roehlly's avatar
Yannick Roehlly committed
302 303 304 305 306 307
    def upgrade_base(self):
        """ Upgrade the table schemas in the database
        """
        if self.is_writable:
            BASE.metadata.create_all(ENGINE)
        else:
308
            raise Exception('The database is not writable.')
Yannick Roehlly's avatar
Yannick Roehlly committed
309 310 311 312 313 314 315 316 317

    def close(self):
        """ Close the connection to the database

        TODO: It would be better to wrap the database use inside a context
        manager.
        """
        self.session.close_all()

318
    def add_m2005(self, ssp_m2005):
Yannick Roehlly's avatar
Yannick Roehlly committed
319
        """
320
        Add a Maraston 2005 SSP to pcigale database
Yannick Roehlly's avatar
Yannick Roehlly committed
321

Yannick Roehlly's avatar
Yannick Roehlly committed
322
        Parameters
Yannick Roehlly's avatar
Yannick Roehlly committed
323
        ----------
324
        ssp: pcigale.base.M2005
325

Yannick Roehlly's avatar
Yannick Roehlly committed
326 327
        """
        if self.is_writable:
328 329
            ssp = _M2005(ssp_m2005)
            self.session.add(ssp)
Yannick Roehlly's avatar
Yannick Roehlly committed
330 331 332 333
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
334
                raise DatabaseInsertError('The SSP is already in the base.')
Yannick Roehlly's avatar
Yannick Roehlly committed
335
        else:
336
            raise Exception('The database is not writable.')
Yannick Roehlly's avatar
Yannick Roehlly committed
337

338
    def get_m2005(self, imf, metallicity):
Yannick Roehlly's avatar
Yannick Roehlly committed
339
        """
340 341
        Query the database for a Maraston 2005 SSP corresponding to the given
        initial mass function and metallicity.
Yannick Roehlly's avatar
Yannick Roehlly committed
342

Yannick Roehlly's avatar
Yannick Roehlly committed
343
        Parameters
Yannick Roehlly's avatar
Yannick Roehlly committed
344
        ----------
345
        imf: string
346
            Initial mass function (ss for Salpeter, kr for Kroupa)
347
        metallicity: float
348 349 350 351
            [Z/H] = Log10(Z/Zsun) - Log10(H/Hsun)

        Returns
        -------
352
        ssp: pcigale.base.M2005
353 354 355 356
            The M2005 object.

        Raises
        ------
357
        DatabaseLookupError: if the requested SSP is not in the database.
Yannick Roehlly's avatar
Yannick Roehlly committed
358 359

        """
360 361 362 363 364 365
        result = self.session.query(_M2005)\
            .filter(_M2005.imf == imf)\
            .filter(_M2005.metallicity == metallicity)\
            .first()
        if result:
            return M2005(result.imf, result.metallicity, result.time_grid,
366
                         result.wavelength_grid, result.info_table,
367
                         result.spec_table)
Yannick Roehlly's avatar
Yannick Roehlly committed
368
        else:
369 370 371 372 373 374 375 376 377 378 379 380 381
            raise DatabaseLookupError(
                "The M2005 SSP for imf <{0}> and metallicity <{1}> is not in "
                "the database.".format(imf, metallicity))

    def get_m2005_parameters(self):
        """Get parameters for the Maraston 2005 stellar models.

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_M2005)
Yannick Roehlly's avatar
Yannick Roehlly committed
382

383
    def add_bc03(self, ssp_bc03):
384 385 386 387 388
        """
        Add a Bruzual and Charlot 2003 SSP to pcigale database

        Parameters
        ----------
389
        ssp: pcigale.data.SspBC03
390 391 392

        """
        if self.is_writable:
393
            ssp = _BC03(ssp_bc03)
394 395 396 397 398
            self.session.add(ssp)
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
399
                raise DatabaseInsertError('The SSP is already in the base.')
400
        else:
401
            raise Exception('The database is not writable.')
402

403 404 405 406 407 408 409
    def get_bc03(self, imf, metallicity):
        """
        Query the database for the Bruzual and Charlot 2003 SSP corresponding
        to the given initial mass function and metallicity.

        Parameters
        ----------
410
        imf: string
411
            Initial mass function (salp for Salpeter, chab for Chabrier)
412
        metallicity: float
413 414 415
            0.02 for Solar metallicity
        Returns
        -------
416
        ssp: pcigale.data.BC03
417 418 419 420
            The BC03 object.

        Raises
        ------
421
        DatabaseLookupError: if the requested SSP is not in the database.
422 423 424 425 426 427 428 429

        """
        result = self.session.query(_BC03)\
            .filter(_BC03.imf == imf)\
            .filter(_BC03.metallicity == metallicity)\
            .first()
        if result:
            return BC03(result.imf, result.metallicity, result.time_grid,
430 431
                        result.wavelength_grid, result.info_table,
                        result.spec_table)
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
        else:
            raise DatabaseLookupError(
                "The BC03 SSP for imf <{0}> and metallicity <{1}> is not in "
                "the database.".format(imf, metallicity))

    def get_bc03_parameters(self):
        """Get parameters for the Bruzual & Charlot 2003 stellar models.

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_BC03)

447
    def add_dl2007(self, models):
448
        """
449
        Add a list of Draine and Li (2007) models to the database.
450 451 452

        Parameters
        ----------
453
        models: list of pcigale.data.DL2007 objects
454 455 456

        """
        if self.is_writable:
457 458
            for model in models:
                self.session.add(_DL2007(model))
459 460 461 462
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
463 464
                raise DatabaseInsertError(
                    'The DL07 model is already in the base.')
465
        else:
466
            raise Exception('The database is not writable.')
467

468
    def get_dl2007(self, qpah, umin, umax):
Yannick Roehlly's avatar
Yannick Roehlly committed
469
        """
470 471
        Get the Draine and Li (2007) model corresponding to the given set of
        parameters.
Yannick Roehlly's avatar
Yannick Roehlly committed
472

Yannick Roehlly's avatar
Yannick Roehlly committed
473
        Parameters
Yannick Roehlly's avatar
Yannick Roehlly committed
474
        ----------
475 476 477 478 479 480
        qpah: float
            Mass fraction of PAH
        umin: float
            Minimum radiation field
        umax: float
            Maximum radiation field
Yannick Roehlly's avatar
Yannick Roehlly committed
481 482 483

        Returns
        -------
484
        model: pcigale.data.DL2007
485
            The Draine and Li (2007) model.
486 487 488

        Raises
        ------
489
        DatabaseLookupError: if the requested model is not in the database.
490

Yannick Roehlly's avatar
Yannick Roehlly committed
491
        """
492 493 494 495
        result = (self.session.query(_DL2007).
                  filter(_DL2007.qpah == qpah).
                  filter(_DL2007.umin == umin).
                  filter(_DL2007.umax == umax).
Yannick Roehlly's avatar
Yannick Roehlly committed
496 497
                  first())
        if result:
498 499
            return DL2007(result.qpah, result.umin, result.umax, result.wave,
                          result.lumin)
Yannick Roehlly's avatar
Yannick Roehlly committed
500
        else:
501
            raise DatabaseLookupError(
502 503
                "The DL2007 model for qpah <{0}>, umin <{1}>, and umax <{2}> "
                "is not in the database.".format(qpah, umin, umax))
Yannick Roehlly's avatar
Yannick Roehlly committed
504

505 506
    def get_dl2007_parameters(self):
        """Get parameters for the DL2007 models.
Yannick Roehlly's avatar
Yannick Roehlly committed
507 508 509

        Returns
        -------
510 511 512 513
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_DL2007)
514

515
    def add_dl2014(self, models):
516
        """
517
        Add a list of updated Draine and Li (2007) models to the database.
518 519 520

        Parameters
        ----------
521
        models: list of pcigale.data.DL2014 objects
522 523 524

        """
        if self.is_writable:
525 526
            for model in models:
                self.session.add(_DL2014(model))
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError(
                    'The updated DL07 model is already in the base.')
        else:
            raise Exception('The database is not writable.')

    def get_dl2014(self, qpah, umin, umax, alpha):
        """
        Get the Draine and Li (2007) model corresponding to the given set of
        parameters.

        Parameters
        ----------
        qpah: float
            Mass fraction of PAH
        umin: float
            Minimum radiation field
        umin: float
            Maximum radiation field
        alpha: float
            Powerlaw slope dU/dM∝U¯ᵅ

        Returns
        -------
        model: pcigale.data.DL2014
            The updated Draine and Li (2007) model.

        Raises
        ------
        DatabaseLookupError: if the requested model is not in the database.

        """
        result = (self.session.query(_DL2014).
                  filter(_DL2014.qpah == qpah).
                  filter(_DL2014.umin == umin).
                  filter(_DL2014.umax == umax).
                  filter(_DL2014.alpha == alpha).
                  first())
        if result:
            return DL2014(result.qpah, result.umin, result.umax, result.alpha,
                          result.wave, result.lumin)
        else:
            raise DatabaseLookupError(
                "The DL2014 model for qpah <{0}>, umin <{1}>, umax <{2}>, and "
Médéric Boquien's avatar
Médéric Boquien committed
574
                "alpha <{3}> is not in the database."
575 576 577 578 579 580 581 582 583 584 585 586
                .format(qpah, umin, umax, alpha))

    def get_dl2014_parameters(self):
        """Get parameters for the DL2014 models.

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_DL2014)

587
    def add_dale2014(self, models):
588 589 590 591 592
        """
        Add Dale et al (2014) templates the collection.

        Parameters
        ----------
593
        models: list of pcigale.data.Dale2014 objects
Yannick Roehlly's avatar
Yannick Roehlly committed
594 595

        """
596 597

        if self.is_writable:
598 599
            for model in models:
                self.session.add(_Dale2014(model))
600 601 602 603 604 605
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError(
                    'The Dale2014 template is already in the base.')
Yannick Roehlly's avatar
Yannick Roehlly committed
606
        else:
607
            raise Exception('The database is not writable.')
Yannick Roehlly's avatar
Yannick Roehlly committed
608

609
    def get_dale2014(self, frac_agn, alpha):
610 611 612 613 614 615
        """
        Get the Dale et al (2014) template corresponding to the given set of
        parameters.

        Parameters
        ----------
616
        frac_agn: float
617 618
            contribution of the AGN to the IR luminosity
        alpha: float
619 620
            alpha corresponding to the updated Dale & Helou (2002) star
            forming template.
621

622 623
        Returns
        -------
624
        template: pcigale.data.Dale2014
625 626 627 628
            The Dale et al. (2014) IR template.

        Raises
        ------
629
        DatabaseLookupError: if the requested template is not in the database.
630

631
        """
632 633 634
        result = (self.session.query(_Dale2014).
                  filter(_Dale2014.fracAGN == frac_agn).
                  filter(_Dale2014.alpha == alpha).
635 636
                  first())
        if result:
637 638
            return Dale2014(result.fracAGN, result.alpha, result.wave,
                            result.lumin)
639
        else:
640 641 642
            raise DatabaseLookupError(
                "The Dale2014 template for frac_agn <{0}> and alpha <{1}> "
                "is not in the database.".format(frac_agn, alpha))
643

644 645 646 647 648 649 650 651 652 653
    def get_dale2014_parameters(self):
        """Get parameters for the Dale 2014 models.

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_Dale2014)

654
    def add_fritz2006(self, models):
655 656 657 658 659
        """
        Add a Fritz et al. (2006) AGN model to the database.

        Parameters
        ----------
660
        models: list of pcigale.data.Fritz2006 objects
661 662 663

        """
        if self.is_writable:
664 665
            for model in models:
                self.session.add(_Fritz2006(model))
666 667 668 669 670 671 672 673 674
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError(
                    'The agn model is already in the base.')
        else:
            raise Exception('The database is not writable.')

675
    def get_fritz2006(self, r_ratio, tau, beta, gamma, opening_angle, psy):
676 677 678 679 680
        """
        Get the Fritz et al. (2006) AGN model corresponding to the number.

        Parameters
        ----------
681
        r_ratio: float
682
            Ratio of the maximum and minimum radii of the dust torus.
683
        tau: float
684
            Tau at 9.7µm
685
        beta: float
686
            Beta
687
        gamma: float
688
            Gamma
689
        opening_angle: float
690
            Opening angle of the dust torus.
691
        psy: float
692
            Angle between AGN axis and line of sight.
693
        wave: array of float
694
            Wavelength grid in nm.
695
        lumin_therm: array of float
696
            Luminosity density of the dust torus at each wavelength in W/nm.
697
        lumin_scatt: array of float
698
            Luminosity density of the scattered emission at each wavelength
699
            in W/nm.
700
        lumin_agn: array of float
701 702
            Luminosity density of the central AGN at each wavelength in W/nm.

703 704 705

        Returns
        -------
706
        agn: pcigale.data.Fritz2006
707 708 709 710
            The AGN model.

        Raises
        ------
711
        DatabaseLookupError: if the requested template is not in the database.
712 713

        """
714
        result = (self.session.query(_Fritz2006).
715 716 717 718 719 720
                  filter(_Fritz2006.r_ratio == r_ratio).
                  filter(_Fritz2006.tau == tau).
                  filter(_Fritz2006.beta == beta).
                  filter(_Fritz2006.gamma == gamma).
                  filter(_Fritz2006.opening_angle == opening_angle).
                  filter(_Fritz2006.psy == psy).
721
                  first())
722
        if result:
723
            return Fritz2006(result.r_ratio, result.tau, result.beta,
724 725 726
                             result.gamma, result.opening_angle, result.psy,
                             result.wave, result.lumin_therm,
                             result.lumin_scatt, result.lumin_agn)
727
        else:
728 729
            raise DatabaseLookupError(
                "The Fritz2006 model is not in the database.")
730

731 732
    def get_fritz2006_parameters(self):
        """Get parameters for the Fritz 2006 AGN models.
733 734 735

        Returns
        -------
736 737 738 739
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_Fritz2006)
740

741
    def add_nebular_lines(self, models):
742
        """
743 744 745
        Add ultraviolet and optical line templates to the database.
        """
        if self.is_writable:
746 747
            for model in models:
                self.session.add(_NebularLines(model))
748 749 750 751 752
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise Exception('The line is already in the base')
753
        else:
754
            raise Exception('The database is not writable')
755

756
    def get_nebular_lines(self, metallicity, logU):
757 758
        """
        Get the line ratios corresponding to the given set of parameters.
759

760 761 762 763 764
        Parameters
        ----------
        metallicity: float
            Gas phase metallicity
        logU: float
765
            Ionisation parameter
766
        """
767 768 769 770
        result = (self.session.query(_NebularLines).
                  filter(_NebularLines.metallicity == metallicity).
                  filter(_NebularLines.logU == logU).
                  first())
771
        if result:
772 773
            return NebularLines(result.metallicity, result.logU, result.name,
                                result.wave, result.ratio)
774 775 776
        else:
            return None

777 778 779 780 781 782 783 784 785 786
    def get_nebular_lines_parameters(self):
        """Get parameters for the nebular lines.

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_NebularLines)

787
    def add_nebular_continuum(self, models):
788 789 790 791
        """
        Add nebular continuum templates to the database.
        """
        if self.is_writable:
792 793
            for model in models:
                self.session.add(_NebularContinuum(model))
794 795 796 797 798 799 800 801 802
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise Exception('The continuum template is already in the '
                                'base')
        else:
            raise Exception('The database is not writable')

803 804 805 806 807 808 809 810 811
    def get_nebular_continuum(self, metallicity, logU):
        """
        Get the nebular continuum corresponding to the given set of parameters.

        Parameters
        ----------
        metallicity: float
            Gas phase metallicity
        logU: float
812
            Ionisation parameter
813 814 815 816 817 818 819 820 821 822 823
        """
        result = (self.session.query(_NebularContinuum).
                  filter(_NebularContinuum.metallicity == metallicity).
                  filter(_NebularContinuum.logU == logU).
                  first())
        if result:
            return NebularContinuum(result.metallicity, result.logU,
                                    result.wave, result.lumin)
        else:
            return None

824 825
    def get_nebular_continuum_parameters(self):
        """Get parameters for the nebular continuum.
826 827 828 829 830 831

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
832
        return self._get_parameters(_NebularContinuum)
833

834 835 836
    def add_schreiber2016(self, models):
        """
        Add Schreiber et al (2016) templates the collection.
837

838 839 840
        Parameters
        ----------
        models: list of pcigale.data.Schreiber2016 objects
841

842
        """
843

844 845 846 847 848 849 850 851 852 853 854 855 856 857
        if self.is_writable:
            for model in models:
                self.session.add(_Schreiber2016(model))
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError(
                  'The Schreiber2016 template is already in the base.')
        else:
            raise Exception('The database is not writable.')

    def get_schreiber2016(self, type, tdust):
        """
858 859 860
        Get the Schreiber et al (2016) template corresponding to the given set
        of parameters.

861 862 863 864 865 866
        Parameters
        ----------
        type: float
        Dust template or PAH template
        tdust: float
        Dust temperature
867

868 869 870 871
        Returns
        -------
        template: pcigale.data.Schreiber2016
        The Schreiber et al. (2016) IR template.
872

873 874 875
        Raises
        ------
        DatabaseLookupError: if the requested template is not in the database.
876

877 878
        """
        result = (self.session.query(_Schreiber2016).
879 880 881
                  filter(_Schreiber2016.type == type).
                  filter(_Schreiber2016.tdust == tdust).
                  first())
882 883
        if result:
            return Schreiber2016(result.type, result.tdust, result.wave,
884
                                 result.lumin)
885 886 887 888 889 890 891
        else:
            raise DatabaseLookupError(
                "The Schreiber2016 template for type <{0}> and tdust <{1}> "
                "is not in the database.".format(type, tdust))

    def get_schreiber2016_parameters(self):
        """Get parameters for the Scnreiber 2016 models.
892

893 894 895 896 897 898 899
        Returns
        -------
        paramaters: dictionary
        dictionary of parameters and their values
        """
        return self._get_parameters(_Schreiber2016)

900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962
    def add_themis(self, models):
        """
        Add a list of Jones et al (2017) models to the database.

        Parameters
        ----------
        models: list of pcigale.data.THEMIS objects

        """
        if self.is_writable:
            for model in models:
                self.session.add(_THEMIS(model))
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError(
                    'Error.')
        else:
            raise Exception('The database is not writable.')

    def get_themis(self, qhac, umin, umax, alpha):
        """
        Get the Jones et al (2017) model corresponding to the given set of
        parameters.

        Parameters
        ----------
        qhac: float
            Mass fraction of hydrocarbon solids i.e., a-C(:H) smaller than
        1.5 nm, also known as HAC
        umin: float
            Minimum radiation field
        umin: float
            Maximum radiation field
        alpha: float
            Powerlaw slope dU/dM∝U¯ᵅ

        Returns
        -------
        model: pcigale.data.THEMIS
            The Jones et al (2017) model.

        Raises
        ------
        DatabaseLookupError: if the requested model is not in the database.

        """
        result = (self.session.query(_THEMIS).
                  filter(_THEMIS.qhac == qhac).
                  filter(_THEMIS.umin == umin).
                  filter(_THEMIS.umax == umax).
                  filter(_THEMIS.alpha == alpha).
                  first())
        if result:
            return THEMIS(result.qhac, result.umin, result.umax, result.alpha,
                          result.wave, result.lumin)
        else:
            raise DatabaseLookupError(
                "The THEMIS model for qhac <{0}>, umin <{1}>, umax <{2}>, and "
                "alpha <{3}> is not in the database."
                .format(qhac, umin, umax, alpha))

963 964
    def _get_parameters(self, schema):
        """Generic function to get parameters from an arbitrary schema.
965 966 967

        Returns
        -------
968 969
        parameters: dictionary
            Dictionary of parameters and their values
970 971
        """

972 973 974
        return {k.name: np.sort(
                [v[0] for v in set(self.session.query(schema).values(k))])
                for k in class_mapper(schema).primary_key}
975

976
    def add_filter(self, pcigale_filter):
977
        """
978
        Add a filter to pcigale database.
979

980 981
        Parameters
        ----------
982
        pcigale_filter: pcigale.data.Filter
983
        """
984 985 986 987 988 989 990 991 992
        if self.is_writable:
            self.session.add(_Filter(pcigale_filter))
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError('The filter is already in the base.')
        else:
            raise Exception('The database is not writable.')
993

994 995 996 997 998 999 1000 1001 1002 1003
    def get_themis_parameters(self):
        """Get parameters for the THEMIS models.

        Returns
        -------
        paramaters: dictionary
            dictionary of parameters and their values
        """
        return self._get_parameters(_THEMIS)

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
    def add_filters(self, pcigale_filters):
        """
        Add a list of filters to the pcigale database.

        Parameters
        ----------
        pcigale_filters: list of pcigale.data.Filter objects
        """
        if self.is_writable:
            for pcigale_filter in pcigale_filters:
                self.session.add(_Filter(pcigale_filter))
            try:
                self.session.commit()
            except exc.IntegrityError:
                self.session.rollback()
                raise DatabaseInsertError('The filter is already in the base.')
        else:
            raise Exception('The database is not writable.')

1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
    def del_filter(self, name):
        """
        Delete a filter from the pcigale database.

        Parameters
        ----------
        name: name of the filter to be deleted
        """
        if self.is_writable:
            if name in self.get_filter_names():
                (self.session.query(_Filter).
                 filter(_Filter.name == name).delete())
                try:
                    self.session.commit()
                except exc.IntegrityError:
                    raise Exception('The database is not writable.')
        else:
            raise DatabaseLookupError(
                "The filter <{0}> is not in the database".format(name))

1043
    def get_filter(self, name):
1044
        """
1045
        Get a specific filter from the collection
1046

1047 1048
        Parameters
        ----------
1049
        name: string
1050
            Name of the filter
1051 1052 1053

        Returns
        -------
1054
        filter: pcigale.base.Filter
1055
            The Filter object.
1056

1057 1058
        Raises
        ------
1059
        DatabaseLookupError: if the requested filter is not in the database.
1060 1061

        """
1062 1063 1064 1065
        result = (self.session.query(_Filter).
                  filter(_Filter.name == name).
                  first())
        if result:
1066
            return Filter(result.name, result.description, result.trans_table,
1067
                          result.pivot_wavelength)
1068 1069 1070
        else:
            raise DatabaseLookupError(
                "The filter <{0}> is not in the database".format(name))
1071

1072 1073
    def get_filter_names(self):
        """Get the list of the name of the filters in the database.
Yannick Roehlly's avatar
Yannick Roehlly committed
1074 1075 1076

        Returns
        -------
1077 1078 1079 1080
        names: list
            list of the filter names