├── example.tar.gz ├── .gitignore ├── pyproject.toml ├── CONTRIBUTING.md ├── LICENSE ├── gleam ├── read_files.py ├── matplotlibparams.py ├── main.py ├── __init__.py ├── constants.py ├── spectra_operations.py ├── plot_gaussian.py └── gaussian_fitting.py ├── README.md └── poetry.lock /example.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiwavelength/gleam/HEAD/example.tar.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.png 3 | *.pyc 4 | .vscode 5 | .pytest_cache 6 | .mypy_cache 7 | gleam.egg-info/ 8 | *tar.gz 9 | .venv* 10 | dist/ 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "astro-gleam" 3 | version = "1.4.0" 4 | description = "Galaxy Line Emission and Absorption Modeling" 5 | authors = ["Andra Stroe ", "Victor-Nicolae Savu "] 6 | packages = [ 7 | { include = "gleam" } 8 | ] 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.8" 12 | lmfit = "^1.0.0" 13 | matplotlib = "=3.7.1" 14 | astropy = "^5.2.2" 15 | colorama = "^0.4.3" 16 | numpy = "^1.21" 17 | click = "^8.1.3" 18 | pydantic = "^1.6.2" 19 | pyyaml = "^6.0" 20 | 21 | [tool.poetry.dev-dependencies] 22 | mypy = "^1.3.0" 23 | black = "^23.3.0" 24 | ipython = "^7.16.3" 25 | rope = "^1.8.0" 26 | 27 | [tool.poetry.scripts] 28 | gleam = 'gleam:pipeline' 29 | 30 | [build-system] 31 | requires = ["poetry-core"] 32 | build-backend = "poetry.core.masonry.api" 33 | 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to **gleam** 2 | 3 | Thank you for taking the time to contribute to **gleam**! 4 | 5 | ### Join 6 | 7 | The **gleam** community follows the [Astropy Community Code of Conduct](https://www.astropy.org/code_of_conduct.html). 8 | If you have read it, then you know that the most important contribution is to treat others with respect. 9 | 10 | ### Share 11 | 12 | A great way to contribute to **gleam** is to use it in your projects and then talk about it. Share 13 | your experience with other people who might be interested in using `gleam` and direct them to the 14 | [GitHub repository page](https://github.com/multiwavelength/gleam). 15 | 16 | ### Suggest 17 | 18 | Your experience using **gleam** is important, so any time you feel like it could be improved (e.g. you 19 | encounter a problem or **gleam** doesn't meet your needs), please open an issue on this repository and 20 | share your suggestions. 21 | 22 | ### Code 23 | 24 | If you've modified **gleam** to better suite your project, chances are you've made it better for 25 | many other people and for their projects. Please consider sharing your changes with the community 26 | by opening a Pull Request to this repository. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Andra Stroe 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /gleam/read_files.py: -------------------------------------------------------------------------------- 1 | __author__ = "Andra Stroe" 2 | __version__ = "0.1" 3 | 4 | import glob 5 | import sys 6 | 7 | import numpy as np 8 | from astropy.io import fits 9 | from astropy.table import QTable, Column 10 | from astropy import units as u 11 | from colorama import Fore 12 | 13 | import gleam.constants as c 14 | 15 | 16 | def read_lof(file1): 17 | """For each sample, telescope setup and pointing, it reads the metadata file, 18 | which contains a list of the sources and their properties. 19 | Input: 20 | file1: metadata file in ascii or fits format 21 | The format of the head file is the following 22 | # Setup Pointing SourceNumber Sample Redshift 23 | Return: 24 | Astropy Table with measurements of interest: the source number, the 25 | parent sample, the telescope/instrument and pointing and the redshift. 26 | Throws error if the file is not of the right type. 27 | """ 28 | try: 29 | table = QTable.read(file1, format="fits") 30 | return table 31 | except: 32 | try: 33 | table = QTable.read(file1, format="ascii.commented_header") 34 | return table 35 | except: 36 | print(Fore.RED + "Cannot find metadata redshift file") 37 | sys.exit("Error!") 38 | 39 | 40 | def naming_convention(data_path, sample, source_number, setup, pointing, mod): 41 | """ 42 | Naming convention for files which starts with type of file and is followed 43 | by details about the source and setup, in order: parent sample, setup, 44 | pointing and source number. 45 | """ 46 | return ( 47 | f"{data_path}/{mod}.{sample}.{setup}.{pointing}.{source_number.astype(int):03d}" 48 | ) 49 | -------------------------------------------------------------------------------- /gleam/matplotlibparams.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | 3 | matplotlib.use("TkAgg") 4 | from matplotlib import rc 5 | import matplotlib.pyplot as plt 6 | 7 | """ 8 | Some reasonable plotting parameters. Feel free to change as per your taste. 9 | """ 10 | 11 | plt.rc("text", usetex=True) 12 | plt.rc( 13 | "text.latex", 14 | preamble=r"\usepackage{amsmath} \usepackage{amsmath} \usepackage[mode=text,per-mode=symbol]{siunitx} \sisetup{detect-all} \usepackage{helvet} \usepackage{textgreek} \usepackage{sansmath} \sansmath", 15 | ) 16 | 17 | 18 | ########################################################################### 19 | # # 20 | # SET PLOTTING PARAMETERS # 21 | # # 22 | # plotting parameters 23 | size_major = 8.0 24 | size_minor = 4.0 25 | thick = 1.0 26 | 27 | ms1 = 10 28 | t1 = 0.2 29 | w1 = 0.02 30 | w2 = 0.1 31 | l1 = 0.2 32 | 33 | 34 | plt.rcParams["axes.linewidth"] = thick 35 | plt.rcParams["xtick.major.size"] = size_major 36 | plt.rcParams["ytick.major.size"] = size_major 37 | plt.rcParams["xtick.minor.size"] = size_minor 38 | plt.rcParams["ytick.minor.size"] = size_minor 39 | 40 | # Increase the tick-mark widths as well as the widths of lines 41 | # used to draw marker edges to be consistent with the other figure 42 | # linewidths (defaults are all 0.5) 43 | plt.rcParams["xtick.major.width"] = thick 44 | plt.rcParams["ytick.major.width"] = thick 45 | plt.rcParams["xtick.minor.width"] = thick 46 | plt.rcParams["ytick.minor.width"] = thick 47 | plt.rcParams["lines.markeredgewidth"] = thick 48 | plt.rcParams["lines.linewidth"] = thick 49 | plt.rcParams["lines.antialiased"] = True 50 | 51 | plt.rcParams.update({"font.size": 15}) 52 | plt.rcParams["axes.titlesize"] = 30 53 | 54 | plt.rcParams["figure.figsize"] = 20, 10 55 | plt.rcParams["savefig.dpi"] = 300 56 | # # 57 | # # 58 | # # 59 | ########################################################################### 60 | -------------------------------------------------------------------------------- /gleam/main.py: -------------------------------------------------------------------------------- 1 | __author__ = "Andra Stroe" 2 | __version__ = "0.1" 3 | 4 | import os, sys 5 | from contextlib import contextmanager 6 | 7 | import numpy as np 8 | import astropy 9 | from astropy import units as u 10 | from astropy.table import QTable, Table, Column 11 | from astropy.io import fits 12 | from colorama import Fore 13 | from colorama import init 14 | 15 | init(autoreset=True) 16 | 17 | import gleam.read_files as rf 18 | import gleam.gaussian_fitting as gf 19 | import gleam.plot_gaussian as pg 20 | import gleam.spectra_operations as so 21 | 22 | 23 | @contextmanager 24 | def fake(): 25 | yield lambda *_: None 26 | 27 | 28 | def run_main(spectrum_file, target, inspect, plot, verbose, bin1, c): 29 | """ 30 | For a target/galaxy, read the spectrum and perform the line fitting for each 31 | line within the list of lines 32 | Input: 33 | spectrum_file: target spectrum file 34 | target: Astropy row with target properties 35 | inspect: if true, show the plots; otherwise write to disk 36 | plot: plot figures to disk 37 | verbose: print full lmfit output 38 | bin1: number of adjacent spectral pixels to be binned 39 | c: full configuration file 40 | Output: 41 | fits of emission lines and plots for each fitted lines 42 | """ 43 | 44 | data_path = os.path.dirname(spectrum_file) 45 | print( 46 | f"Now working in {data_path} " 47 | + f'on {target["Sample"]} in {target["Setup"]} + {target["Pointing"]} ' 48 | + f'on source {target["SourceNumber"]} at z={target["Redshift"]:1.3f}.' 49 | ) 50 | 51 | # Read spectrum for the current source from outside file 52 | spectrum = QTable.read(spectrum_file) 53 | 54 | if bin1 > 1: 55 | spectrum = so.bin_spectrum(spectrum, bin1) 56 | 57 | # Given its redshift, calculate restframe spectrum 58 | spectrum = so.add_restframe(spectrum, target["Redshift"]) 59 | 60 | # Configuration for curret source 61 | config = c( 62 | target["Sample"], target["Setup"], target["Pointing"], target["SourceNumber"] 63 | ) 64 | 65 | # Read in line table 66 | line_list = config.line_list 67 | 68 | # Read in file with sky bands 69 | sky = config.sky_list 70 | 71 | # Find groups of nearby lines in the input table that will be fit together 72 | line_groups = so.group_lines(line_list, config.fitting.tolerance/2.) 73 | 74 | overview = ( 75 | pg.overview_plot( 76 | target, 77 | data_path, 78 | line_groups, 79 | spectrum, 80 | config.fitting.cont_width, 81 | config.resolution / (1 + target["Redshift"]), 82 | sky, 83 | ) 84 | if plot 85 | else fake() 86 | ) 87 | with overview as plot_line: 88 | tables = [] 89 | # Set the name to the exported plot in png format 90 | for spectrum_fit, spectrum_line, lines in gf.fit_lines( 91 | target, 92 | spectrum, 93 | line_list, 94 | line_groups, 95 | config.fitting.center, 96 | verbose, 97 | sky, 98 | config.fitting.cont_width, 99 | config.fitting.mask_width, 100 | config.fitting.w, 101 | config.fitting.SN_limit, 102 | config.resolution / (1 + target["Redshift"]), 103 | config.cosmology.cosmo, 104 | ): 105 | # Make a plot/fit a spectrum if the line in within the rest-frame 106 | # spectral coverage of the source 107 | if plot: 108 | pg.plot_spectrum( 109 | data_path, 110 | target, 111 | spectrum, 112 | spectrum_line, 113 | spectrum_fit, 114 | lines["line"], 115 | line_list["latex"], 116 | line_list["wavelength"], 117 | lines["wavelength"], 118 | inspect, 119 | config.fitting.cont_width, 120 | config.resolution / (1 + target["Redshift"]), 121 | sky, 122 | ) 123 | if spectrum_fit is not None: 124 | for (line_fit, line) in zip(spectrum_fit.lines, lines): 125 | tables.append( 126 | Table(line_fit.as_fits_table(line), masked=True, copy=False) 127 | ) 128 | plot_line( 129 | lines, 130 | spectrum_fit, 131 | config.resolution / (1 + target["Redshift"]), 132 | sky, 133 | ) 134 | 135 | try: 136 | outtable = astropy.table.vstack(tables) 137 | outtable = Table(outtable, masked=True, copy=False) 138 | outfile = "{}.fits".format( 139 | rf.naming_convention( 140 | data_path, 141 | target["Sample"], 142 | target["SourceNumber"], 143 | target["Setup"], 144 | target["Pointing"], 145 | "linefits", 146 | ) 147 | ) 148 | outtable.write(outfile, overwrite=True) 149 | except: 150 | print( 151 | Fore.YELLOW 152 | + f"Warning: no emission line fits in " 153 | + f'on {target["Sample"]} in {target["Setup"]} + {target["Pointing"]} ' 154 | + f'on source {target["SourceNumber"]} at z={target["Redshift"]:1.3f}.' 155 | ) 156 | -------------------------------------------------------------------------------- /gleam/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "Andra Stroe" 2 | __version__ = "0.1" 3 | 4 | import os, sys 5 | import warnings 6 | import glob 7 | from multiprocessing import Pool 8 | from functools import reduce 9 | from typing import Tuple 10 | 11 | import numpy as np 12 | from astropy.io import fits 13 | from astropy.table import vstack, QTable 14 | import click 15 | from colorama import Fore 16 | 17 | import gleam.main 18 | import gleam.read_files as rf 19 | import gleam.constants as c 20 | 21 | warnings.filterwarnings("ignore") 22 | 23 | 24 | class Targets: 25 | """ 26 | Read and stack all metadata files found into a single table. 27 | """ 28 | def __init__(self, filter: str) -> None: 29 | find_meta = glob.glob(filter, recursive=True) 30 | if not find_meta: 31 | sys.exit(Fore.RED + "Error! Cannot find any metadata files.") 32 | try: 33 | targets = vstack( 34 | [rf.read_lof(list_of_targets) for list_of_targets in find_meta] 35 | ) 36 | targets["key"] = reduce( 37 | np.char.add, 38 | ( 39 | targets["Sample"], 40 | ".", 41 | targets["Setup"], 42 | ".", 43 | targets["Pointing"], 44 | ".", 45 | targets["SourceNumber"].astype(str), 46 | ), 47 | ) 48 | targets.add_index("key") 49 | self._targets: QTable = targets 50 | except ValueError: 51 | sys.exit( 52 | Fore.RED 53 | + "Error! Cannot stack metadata files that contain units with those that don't." 54 | ) 55 | 56 | def __getitem__(self, key: Tuple[str, str, str, int]): 57 | """ 58 | Select a unique row of source properties matching the unique identifies 59 | of each source, ie its sample, setup, pointing and source number. 60 | Input: 61 | tuple with key identifiers of a source 62 | Return: 63 | a unique table row with properties of the selected source 64 | """ 65 | sample, setup, pointing, source = key 66 | return self._targets.loc[f"{sample}.{setup}.{pointing}.{source}"] 67 | 68 | 69 | def find_source_properties(find_spectra, targets): 70 | """ 71 | For a sample of sources, find matches in the metadata file. 72 | Input: 73 | find_spectra: globbed list of spectrum file names 74 | targets: stack of metadata files 75 | Return: 76 | unique combination of spectrum and its corresponding properties 77 | """ 78 | for spectrum_file in find_spectra: 79 | # Get unique sample, setup, pointing and source names 80 | _, sample, setup, pointing, source, *_ = os.path.basename(spectrum_file).split( 81 | "." 82 | ) 83 | source = int(source) 84 | 85 | # Find source is metadata file 86 | try: 87 | target = targets[sample, setup, pointing, source] 88 | except KeyError: 89 | print( 90 | Fore.RED 91 | + f"Error! Cannot find source {sample}.{setup}.{pointing}.{source} in any metadata file. Skipping." 92 | ) 93 | continue 94 | if isinstance(target, QTable): 95 | print(type(target)) 96 | print( 97 | Fore.RED 98 | + f"Error! Source {sample}.{setup}.{pointing}.{source} appears in multiple metadata files. Skipping." 99 | ) 100 | continue 101 | yield (spectrum_file, target) 102 | 103 | 104 | # Define command line arguments 105 | @click.command() 106 | @click.option("--path", default=".", help='Path to recursively look for metadata files and spectra. See --spectra for overrides.') 107 | @click.option("--spectra", help='Filter for spectra file paths. e.g. "./**/spec1d.Cosmos.Keck.P1.*.fits" to select all sources in the Cosmos sample observed with Keck in pointing P1.') 108 | @click.option("--config", default="gleamconfig.yaml", help='Configuration file in YAML format.') 109 | @click.option("--plot", is_flag=True, help='Save plots of spectrum with emission lines fits next to the corresponding spectrum file.') 110 | @click.option("--inspect", is_flag=True, help='Show interactive plots.') 111 | @click.option("--verbose", is_flag=True, help='Print full output from LMFIT.') 112 | @click.option("--bin", default=1, help='Bin the spectrum before fitting.') 113 | @click.option("--nproc", default=8, type=int, help='Number of threads.') 114 | def pipeline(path, spectra, config, plot, inspect, verbose, bin, nproc): 115 | # Read configuration file 116 | config = c.read_config(config) 117 | 118 | # Find all the metadata files as the targets inside them 119 | targets = Targets(f"{path}/**/meta.*") 120 | 121 | # Find all the spectrum files 122 | if spectra is None: 123 | spectra = f"{path}/**/spec1d*fits" 124 | find_spectra = sorted(glob.glob(f"{spectra}", recursive=True)) 125 | 126 | # Make a list of all sources with their properties 127 | unique_sources = ( 128 | (*unique_source, inspect, plot, verbose, bin, config) 129 | for unique_source in find_source_properties(find_spectra, targets) 130 | ) 131 | 132 | # Set up multithread processing as executing the fitting on different 133 | # sources is trivially parallelizable 134 | nproc = 1 if inspect else nproc 135 | with Pool(nproc) as p: 136 | p.starmap(gleam.main.run_main, unique_sources) 137 | 138 | 139 | if __name__ == "__main__": 140 | pipeline() 141 | -------------------------------------------------------------------------------- /gleam/constants.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Contains a list of constants and user defined units 3 | 4 | """ 5 | __author__ = "Andra Stroe" 6 | __version__ = "0.1" 7 | 8 | import yaml 9 | from dataclasses import asdict, is_dataclass, field, dataclass as dat 10 | from typing import Dict, Optional, List, Union, Literal 11 | import functools 12 | 13 | 14 | from pydantic.dataclasses import dataclass 15 | from astropy import units as u 16 | from astropy.table import QTable 17 | from astropy.cosmology import FlatLambdaCDM 18 | from colorama import Fore 19 | 20 | AllLines = Literal["all"] 21 | CenterConstraint = Literal["free", "constrained", "fixed"] 22 | 23 | 24 | class Quantity(u.SpecificTypeQuantity): 25 | """ 26 | Validation of the types of unit for each parameter, to ensure the right type 27 | is being given. 28 | """ 29 | 30 | @classmethod 31 | def __get_validators__(cls): 32 | yield cls.validate 33 | 34 | @classmethod 35 | def validate(cls, v): 36 | return cls(v) 37 | 38 | @dat(frozen=True, eq=True, unsafe_hash=True) 39 | class SourceIdentifier: 40 | sample: str 41 | setup: str 42 | pointing: str 43 | source: int 44 | 45 | @classmethod 46 | def __get_validators__(cls): 47 | yield cls.validate 48 | 49 | @classmethod 50 | def validate(cls, v): 51 | sample, setup, pointing, source = v.split('.') 52 | return cls(sample, setup, pointing, int(source)) 53 | 54 | class Length(Quantity): 55 | _equivalent_unit = u.m 56 | 57 | 58 | class Frequency(Quantity): 59 | _equivalent_unit = (1 / u.s).unit 60 | 61 | 62 | class Temperature(Quantity): 63 | _equivalent_unit = u.K 64 | 65 | 66 | def override(basic, overrides): 67 | """ 68 | Function to override parameters inside a dictionary or a dataclass. Only 69 | items set in overrides will replace their corresponding items in basic. 70 | Function recursively goes inside each item if they are dict and replaces 71 | items inside (element by element). 72 | Input: 73 | basic: reference object which we want to override 74 | overrides: object used to override elements inside basic. 75 | Return: 76 | dict with values from basic replaced with values in overrides, where 77 | they are set. 78 | """ 79 | # Is the reference object is empty set it to an empty dict. This is needed 80 | # to correctly override unset parameters 81 | if basic is None: 82 | basic = {} 83 | # If no overrides are set, just return the reference objct 84 | if overrides is None: 85 | return basic 86 | # Convert inputs to dict. 87 | if is_dataclass(basic): 88 | basic = asdict(basic) 89 | if is_dataclass(overrides): 90 | overrides = asdict(overrides) 91 | # Return the override dict, with items replaced with their values from 92 | # overrides, when they are None. 93 | return { 94 | **basic, 95 | **{ 96 | key: value 97 | for key, value in overrides.items() 98 | if value is not None and not isinstance(value, dict) 99 | }, 100 | **{ 101 | key: override(basic.get(key), value) 102 | for key, value in overrides.items() 103 | if isinstance(value, dict) 104 | }, 105 | } 106 | 107 | 108 | @dataclass 109 | class Cosmology: 110 | """ 111 | Cosmological parameters. Contains sensible default values and checks for 112 | correct types. 113 | """ 114 | 115 | H0: Frequency = field(default_factory=lambda:70 * u.km / (u.Mpc * u.s)) 116 | Om0: float = 0.3 117 | Tcmb0: Temperature = field(default_factory=lambda:2.725 * u.K) 118 | 119 | @property 120 | def cosmo(self): 121 | # Cosmology using Astropy method 122 | return FlatLambdaCDM(H0=self.H0, Om0=self.Om0, Tcmb0=self.Tcmb0) 123 | 124 | 125 | @dataclass 126 | class FittingParametersOverrides: 127 | """ 128 | Class to hold overrides for fitting parameters. The overrides can come from 129 | setups or individual sources. If no overrides are set, then the fitting 130 | parameters are set to the defaults in the FittingParameters class. 131 | """ 132 | 133 | SN_limit: Optional[float] = None 134 | tolerance: Optional[Length] = None 135 | w: Optional[Length] = None 136 | mask_width: Optional[Length] = None 137 | cont_width: Optional[Length] = None 138 | center: Optional[CenterConstraint] = None 139 | 140 | 141 | @dataclass 142 | class FittingParameters: 143 | """ 144 | Paramters needed for the fitting of the lines. Contains sensible default 145 | values and checks for correct types. 146 | """ 147 | 148 | SN_limit: float = 2 149 | tolerance: Length = field(default_factory=lambda:26.0 * u.Angstrom) 150 | w: Length = field(default_factory=lambda:3 * u.Angstrom) 151 | mask_width: Length = field(default_factory=lambda:20 * u.Angstrom) 152 | cont_width: Length = field(default_factory=lambda:70 * u.Angstrom) 153 | center: CenterConstraint = "free" 154 | 155 | 156 | @dataclass 157 | class ConfigOverrides: 158 | """ 159 | Different configuration overrides. The overrides can come from setups or 160 | individual sources. If no overrides are set, then the parameters are set to 161 | the defaults and values (if the object is mandatory and no default can be 162 | set) in the Config class. 163 | """ 164 | 165 | sky: Optional[str] = None 166 | mask_sky: Optional[str] = None 167 | line_table: Optional[str] = None 168 | resolution: Optional[Length] = None 169 | lines: Union[AllLines, None, List[str]] = None 170 | fitting: Optional[FittingParametersOverrides] = None 171 | 172 | 173 | @dataclass 174 | class Config: 175 | """ 176 | An entire configuration, customizable per telescope and per source. 177 | """ 178 | 179 | line_table: str 180 | resolution: Length 181 | sky: Optional[str] = None 182 | mask_sky: bool = False 183 | lines: Union[AllLines, List[str]] = "all" 184 | fitting: FittingParameters = FittingParameters() 185 | cosmology: Cosmology = Cosmology() 186 | 187 | @property 188 | def line_list(self): 189 | """ 190 | Return a QTable containing the lines to be fit for each source. 191 | Input: 192 | self.line_table: Fits table on disk 193 | self.lines: Which lines from the table to select 194 | Output: 195 | QTable 196 | """ 197 | table = QTable.read(self.line_table) 198 | table.sort("wavelength") 199 | if self.lines == "all": 200 | return table 201 | else: 202 | lines = {line.strip() for line in self.lines} 203 | mask = [line.strip() in lines for line in table["line"]] 204 | return table[mask] 205 | 206 | @property 207 | def sky_list(self): 208 | """ 209 | Return a sky table of regions contaminated by sky emission/absorption 210 | that should be masked or None if not masking is necessary. 211 | Input: 212 | self.line_table: Fits table on disk 213 | self.mask_sky: Should the wavelengths affected by sky be masked in the 214 | fitting. 215 | Output: 216 | QTable 217 | """ 218 | # If no sky fits table is set, then no sky lines will be masked 219 | if self.sky is None: 220 | if self.mask_sky == True: 221 | print( 222 | Fore.YELLOW 223 | + f"Warning: You want to mask the sky, but no valid sky catalog was set. Sky will not be masked." 224 | ) 225 | self.mask_sky = False 226 | return None 227 | else: 228 | # If sky fits table exits, decide whether to mask or not based on 229 | # user preferences. 230 | if self.mask_sky is True: 231 | return QTable.read(self.sky) 232 | if self.mask_sky is False: 233 | return None 234 | 235 | 236 | @dataclass 237 | class Constants: 238 | """ 239 | Constants pertaining to cosmology, to fitting procedures and other telescope 240 | or source specific parameters. Contains sensible defaults and check for unit 241 | type correctness. 242 | """ 243 | 244 | cosmology: Optional[Cosmology] = None 245 | 246 | globals: ConfigOverrides = ConfigOverrides() 247 | setups: Dict[str, ConfigOverrides] = field(default_factory=dict) 248 | sources: Dict[SourceIdentifier, ConfigOverrides] = field(default_factory=dict) 249 | 250 | def __call__( 251 | self, sample: str, setup_name: str, pointing: str, source_number: int, 252 | ) -> Config: 253 | # Override the defaults with values set in the used configuration file. 254 | # First override with any global values, then with telescope specific 255 | # values and then with any source specific values. Cosmology is fixed 256 | # for the entire project. 257 | extra = functools.reduce( 258 | override, 259 | [ 260 | {}, 261 | self.globals, 262 | self.setups.get(setup_name), 263 | self.sources.get(SourceIdentifier(sample, setup_name, pointing, source_number)), 264 | {"cosmology": self.cosmology}, 265 | ], 266 | ) 267 | 268 | return Config(**extra) 269 | 270 | 271 | def read_config(config_file) -> Constants: 272 | """ 273 | Read YAML configuration file into a class. Not all parameters have to be 274 | set. It not set, a parameter will be set to the default value. The class has 275 | defaults that the config file will override. The unit types will also be 276 | checked for correctness. 277 | Input: 278 | config_file: path to YAML parameter file 279 | Output: 280 | return a Constants dataclass instance with the defaults and config 281 | overrides. 282 | """ 283 | config = yaml.safe_load(open(config_file).read()) 284 | return Constants(**config) 285 | -------------------------------------------------------------------------------- /gleam/spectra_operations.py: -------------------------------------------------------------------------------- 1 | __author__ = "Andra Stroe" 2 | __version__ = "0.1" 3 | 4 | 5 | import os, sys 6 | from dataclasses import dataclass 7 | from typing import List 8 | import functools 9 | import operator 10 | 11 | import numpy as np 12 | from astropy import units as u 13 | from astropy import constants as const 14 | from astropy.table import QTable, Column 15 | from colorama import Fore 16 | from colorama import init 17 | 18 | init(autoreset=True) 19 | 20 | 21 | def average_(x, n): 22 | """ 23 | Bin an array by averaging n cells together 24 | Input: 25 | x: astropy column 26 | n: number of cells to average 27 | Return: 28 | average each n cells 29 | """ 30 | return np.average(x.reshape((-1, n)), axis=1) 31 | 32 | 33 | def average_err(x, n): 34 | """ 35 | For binning an array by averaging n cells together, propagation of errors by 36 | sqrt(e1**2+e2**2+e3**2.+...+en**2.)/n 37 | Input: 38 | x: astropy column, for error 39 | n: number of cells to average 40 | Return: 41 | geometric mean of errors 42 | """ 43 | return np.sqrt(np.average((x ** 2).reshape((-1, n)), axis=1) / n) 44 | 45 | 46 | def sigma_to_fwhm(sigma): 47 | """ 48 | Convert from Gaussian sigma to FWHM 49 | """ 50 | return sigma * 2.0 * np.sqrt(2.0 * np.log(2.0)) 51 | 52 | 53 | def fwhm_to_sigma(fwhm): 54 | """ 55 | Convert FWHM of 1D Gaussian to sigma 56 | """ 57 | return fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0))) 58 | 59 | 60 | def height_to_amplitude(height, sigma): 61 | """ 62 | Convert height of a 1D Gaussian to the amplitude 63 | """ 64 | return height * sigma * np.sqrt(2 * np.pi) 65 | 66 | 67 | def amplitude_to_height(amplitude, sigma): 68 | """ 69 | Convert amplitude of a 1D Gaussian to the height 70 | """ 71 | return amplitude / (sigma * np.sqrt(2 * np.pi)) 72 | 73 | 74 | def bin_spectrum(spectrum, n=2): 75 | """ 76 | Bin the spectrum by averaging n number of adjacent cells together 77 | Input: 78 | spectrum: astropy spectrum, where: x axis, usually wavelength; y axis, 79 | usually flux; yerr axis, usually stdev on flux 80 | Output: 81 | binned spectrum 82 | """ 83 | tsize = len(spectrum) // n * n 84 | spectrum = spectrum[:tsize] 85 | t = QTable() 86 | 87 | t["wl"] = Column( 88 | average_(spectrum["wl"], n), unit=spectrum["wl"].unit, dtype="f" 89 | ) # Ang 90 | t["flux"] = Column( 91 | average_(spectrum["flux"], n), unit=spectrum["flux"].unit, dtype="f" 92 | ) # Ang 93 | t["stdev"] = Column( 94 | average_err(spectrum["stdev"], n), unit=spectrum["flux"].unit, dtype="f" 95 | ) 96 | 97 | return t 98 | 99 | 100 | def reject_outliers(data, m=2): 101 | """ 102 | Rejects outliers at a certain number of sigma away from the mean 103 | Input: 104 | data: list of input data with possible outlies 105 | m: number of sigma (stdev) away at which the data should be cut 106 | Output: 107 | data: filtered data 108 | mask: where the data should be masked 109 | """ 110 | mask = np.abs(data - np.mean(data)) <= m * np.std(data) 111 | return data[mask], mask 112 | 113 | 114 | def dispersion(wl): 115 | """ 116 | Returns the dispestion (wavelength width per pixel) of a 1D spectrum 117 | Input: 118 | wl: 1D wavelength in some units (preferably Astropy QTable, such that it 119 | has units attached) 120 | Output: 121 | dispersion: single minimum value for dispersion in the spectrum sampled 122 | 123 | ! Writes warning if the spectrum in non uniform 124 | """ 125 | 126 | diff = wl[1:] - wl[0:-1] 127 | diff, mask = reject_outliers(diff, 3) 128 | average = np.mean(diff) 129 | stdev = np.std(diff) 130 | minimum = np.min(diff) 131 | 132 | if stdev / average > 10 ** -3: 133 | print(Fore.YELLOW + "Warning: non-constant dispersion") 134 | return minimum 135 | 136 | 137 | def mask_line(wl, wl_ref, mask_width): 138 | """ 139 | Masks the spectrum around the listed line, within the width specified 140 | Input: 141 | wl: spectrum to be masked; preferable has unit 142 | wl_ref: reference wavelength that we want to mask; preferably has unit 143 | mask_width: width to be used for masking the line 144 | Output: 145 | mask: mask to be applied to the spectrum such that the spectrum now has 146 | the line in question masked away 147 | """ 148 | wl_min = wl_ref - mask_width / 2.0 149 | wl_max = wl_ref + mask_width / 2.0 150 | mask = (wl < wl_min) | (wl > wl_max) 151 | 152 | return mask 153 | 154 | 155 | def spectrum_rms(y): 156 | """ 157 | Calculate the rms of a spectrum, after removing the mean value 158 | """ 159 | rms = np.sqrt(np.mean((y - np.mean(y)) ** 2)) 160 | return rms 161 | 162 | 163 | def mask_atmosphere(wl, z, sky): 164 | """ 165 | Masks the spectrum around prominent optical atmospheric absorption bands 166 | !!! Assumes spectrum has been restframed !!! 167 | Input: 168 | wl: rest-frame wavelength spectrum 169 | z: redshift of the source; used to restframe the sky lines 170 | sky: sky bands in QTable format 171 | Output: 172 | mask: mask to be applied to the spectrum such that the spectrum now has 173 | the absorption features masked 174 | Note: the wavelengths of the A band and B band sky absorption areas are 175 | defined in the constants file 176 | """ 177 | if sky is None: 178 | return np.ones_like(wl.value).astype(bool) 179 | # mask areas of absorption due to sky 180 | absorption = functools.reduce( 181 | operator.or_, 182 | ( 183 | (wl > restframe_wl(band["wavelength_min"], z)) 184 | & (wl < restframe_wl(band["wavelength_max"], z)) 185 | for band in sky 186 | ), 187 | ) 188 | 189 | without_absorption = ~absorption 190 | return without_absorption 191 | 192 | 193 | def restframe_wl(x, z): 194 | """ 195 | Transform a given spectrum x into the restframe, given the redshift 196 | Input: 197 | x: observed wavelengths of a spectrum, in Angstrom or nm for example 198 | z: redshift 199 | Return: 200 | restframe spectrum 201 | """ 202 | return x / (1.0 + z) 203 | 204 | 205 | def restframe_flux(x, z): 206 | """ 207 | Transform a given spectrum flux density x into the restframe, given the 208 | redshift 209 | Input: 210 | x: observed flux density or standard deviation of a spectrum, in 211 | erg/s/cm^2/A for example 212 | z: redshift 213 | Return: 214 | restframe spectrum flux density 215 | """ 216 | return x * (1.0 + z) 217 | 218 | 219 | def add_restframe(spectrum, z): 220 | """ 221 | Add column with restframe spectrum onto the 1d spectrum flux 222 | Input: 223 | spectrum: Astropy table containing the 1d spectrum of a source 224 | z: redshift, determined externally (e.g. specpro) 225 | Output: 226 | spectrum with the new added restframe wavelength column 227 | """ 228 | spectrum.add_column(restframe_wl(spectrum["wl"], z), name="wl_rest") 229 | spectrum.add_column(restframe_flux(spectrum["flux"],z), name="flux_rest") 230 | spectrum.add_column(restframe_flux(spectrum["stdev"],z), name="stdev_rest") 231 | 232 | return spectrum 233 | 234 | 235 | def select_singleline(wl_rest, line, cont_width): 236 | """ 237 | Select the region around an emission line 238 | !!! Assumes the spectrum is restframed 239 | Input: 240 | wl_rest: Astropy table containing the restframe wavelength for a source, 241 | preferably with unit 242 | line: wavelength of the line of interest, preferably with unit 243 | cont_width: width of the region around the lines to be selected, 244 | preferably with unit 245 | Output: 246 | mask to select region around the line of interest 247 | """ 248 | wl_min = line - cont_width 249 | wl_max = line + cont_width 250 | mask = (wl_rest > wl_min) & (wl_rest < wl_max) 251 | return mask 252 | 253 | 254 | def select_lines( 255 | selected_lines, other_lines, spectrum, target_info, sky, cont_width, mask_width, 256 | ): 257 | """ 258 | Masks the spectrum in the vicinity of the line of interest. It should leave 259 | unmasked the actual line and lots of continuum, but mask other neighboring 260 | lines we want to fit, that might contaminate the continuum estimation 261 | Input: 262 | selected_lines: table of all the lines to be fit 263 | other_lines: the other lines in the table that will be masked 264 | spectrum: 1d spectrum, error and wavelength, as read in by 265 | read_files.read_spectrum with extra column for the restframe 266 | wavelength 267 | target: ancillary information on the source, such as RA, DEC or redshift 268 | as produced by read_files.read_lof() 269 | cont_width: amount of wavelength coverage on each side of the line that 270 | will be taken into account 271 | Output: 272 | wl_masked: masked wavelength coverage, with only the line of interest 273 | and the continuum; all other lines masked 274 | """ 275 | z_ref = target_info["Redshift"] 276 | wl_rest = spectrum["wl_rest"] 277 | flux = spectrum["flux_rest"] 278 | # Restframe resolution 279 | res_rest = dispersion(wl_rest) 280 | # mask all lines, but the line we are interested in 281 | masked_otherlines = np.full(np.shape(wl_rest), True) 282 | for line in map(QTable, other_lines): 283 | mask = mask_line(wl_rest, line["wavelength"], mask_width) 284 | masked_otherlines = masked_otherlines & mask 285 | 286 | # select the lines of interest 287 | select_lines = np.full(np.shape(wl_rest), False) 288 | for line in map(QTable, selected_lines): 289 | mask = select_singleline(wl_rest, line["wavelength"], cont_width) 290 | select_lines = select_lines | mask 291 | 292 | # mask the atmospheric lines, if masking them is enabled 293 | masked_atm = mask_atmosphere(wl_rest, z_ref, sky) 294 | masked_all = masked_atm & masked_otherlines & select_lines 295 | 296 | return masked_all 297 | 298 | 299 | def group_lines(line_list, tolerance): 300 | """ 301 | Group together lines within a wavelength tolerance. These will be fit 302 | together as a sum of Gaussian, rathen than independently. 303 | Input: 304 | line_list: Astropy table containing lines and their properties 305 | t: how far from each other can lines be to still be considered a group. 306 | tolerance is read from the constants file 307 | Output: 308 | Groups of lines of type Component as returned by connected components 309 | """ 310 | wl = [(x - tolerance, x + tolerance) for x in line_list["wavelength"]] 311 | line_groups = connected_components(wl, left, right) 312 | return line_groups 313 | 314 | 315 | @dataclass 316 | class Component: 317 | segments: List 318 | beginning: float 319 | ending: float 320 | 321 | 322 | # offline algorithm 323 | def connected_components(segments, left, right): 324 | """ 325 | For a set of segments (in my case wavelength segments), check whether they 326 | overlap and group them together. 327 | Input: 328 | segments: list of pair of the outmost edges of the segment; beginning 329 | and end of the segment 330 | left: function defining how to get the left-most edge 331 | right: function defining how to get the right-most edge 332 | Output: 333 | List of components (as defined in the Component class): grouped list of 334 | each of the segments that overlap, with their overall left and right 335 | side edges 336 | """ 337 | segments = sorted(segments, key=left) 338 | try: 339 | head, *segments = segments 340 | except: 341 | return [] 342 | 343 | ref_component = Component(segments=[head], beginning=left(head), ending=right(head)) 344 | components = [ref_component] 345 | 346 | for segment in segments: 347 | opening = left(segment) 348 | closing = right(segment) 349 | if ref_component.ending > opening: 350 | ref_component.segments.append(segment) 351 | if closing > ref_component.ending: 352 | ref_component.ending = closing 353 | else: 354 | ref_component = Component( 355 | segments=[segment], beginning=opening, ending=closing 356 | ) 357 | components.append(ref_component) 358 | return components 359 | 360 | 361 | def left(x): 362 | return x[0] 363 | 364 | 365 | def right(x): 366 | return x[1] 367 | -------------------------------------------------------------------------------- /gleam/plot_gaussian.py: -------------------------------------------------------------------------------- 1 | __author__ = "Andra Stroe" 2 | __version__ = "0.1" 3 | 4 | from contextlib import contextmanager 5 | from collections import namedtuple 6 | import gc 7 | 8 | import numpy as np 9 | import gleam.matplotlibparams 10 | from matplotlib import pyplot as plt 11 | from mpl_toolkits.axes_grid1.inset_locator import inset_axes 12 | from mpl_toolkits.axes_grid1.inset_locator import ( 13 | TransformedBbox, 14 | BboxPatch, 15 | BboxConnector, 16 | ) 17 | from astropy.visualization import quantity_support 18 | 19 | import gleam.read_files as rf 20 | import gleam.gaussian_fitting as gf 21 | import gleam.spectra_operations as so 22 | 23 | quantity_support() 24 | 25 | 26 | def mark_inset(parent_axes, inset_axes, loc1a=1, loc1b=1, loc2a=2, loc2b=2, **kwargs): 27 | """ 28 | Redefined the mark_inset function. Gives freedom to connect any corner of an 29 | inset plot to any corner of the parent plot. 30 | """ 31 | rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData) 32 | 33 | pp = BboxPatch(rect, fill=False, **kwargs) 34 | parent_axes.add_patch(pp) 35 | 36 | p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1a, loc2=loc1b, **kwargs) 37 | inset_axes.add_patch(p1) 38 | p1.set_clip_on(False) 39 | p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2a, loc2=loc2b, **kwargs) 40 | inset_axes.add_patch(p2) 41 | p2.set_clip_on(False) 42 | 43 | return pp, p1, p2 44 | 45 | 46 | def overplot_lines(ax, linelabels, lineloc): 47 | """ 48 | Overplots emission lines on an already existing plot with axes 49 | Input: 50 | ax: matplolib axis 51 | linelabels: list of latex formatted names for the lines 52 | lineloc: list of wavelengths of the lines (most likely in Ang) 53 | Output: 54 | ax2: returns the new axis in case we want to overplot some more stuff on 55 | top 56 | """ 57 | ax2 = ax.twiny() 58 | 59 | for xc in lineloc: 60 | ax.axvline(x=xc, color="0.8", linestyle="--") 61 | 62 | ax.set_zorder(ax2.get_zorder() + 1) # put ax in front of ax2 63 | ax.patch.set_visible(False) # hide the 'canvas' 64 | ax2.patch.set_visible(True) 65 | ax2.axes.get_xaxis().set_ticks(lineloc) 66 | ax2.axes.xaxis.set_ticklabels(linelabels) 67 | ax2.set_xlim(ax.get_xlim()) 68 | ax2.xaxis.set_label_position("top") 69 | ax2.set_xticklabels(ax2.xaxis.get_majorticklabels(), rotation=90) 70 | return ax2 71 | 72 | 73 | def overplot_sky(ax, z, sky): 74 | """ 75 | Overplots sky bands on an already existing plot with axes 76 | Input: 77 | ax: matplolib axis 78 | z: redshift of the source 79 | sky: sky bands in QTable format 80 | Output: 81 | ax: returns the new axis in case we want to overplot some more stuff on 82 | top 83 | """ 84 | if sky is None: 85 | return 86 | [ 87 | ax.fill_between( 88 | [ 89 | so.restframe_wl(band["wavelength_min"], z), 90 | so.restframe_wl(band["wavelength_max"], z), 91 | ], 92 | ax.get_ylim()[0], 93 | ax.get_ylim()[1], 94 | facecolor="gray", 95 | alpha=0.3, 96 | ) 97 | for band in sky 98 | ] 99 | 100 | 101 | def plot_spectrum( 102 | data_path, 103 | target, 104 | spectrum, 105 | spectrum_line, 106 | spectrum_fit, 107 | line_names, 108 | line_latex, 109 | line_wls, 110 | fitted_wl, 111 | inspect, 112 | cont_width, 113 | rest_spectral_resolution, 114 | sky, 115 | ): 116 | """ 117 | Plots spectrum and a fit to a specific line. Overplots the entire line 118 | catalogue. 119 | Input: 120 | data_path: folder location of where the plot will be saved 121 | target: target structure containing info on the target 122 | spectrum: entire observed spectrum, format from rf.read_spectrum 123 | spectrum_line: observed spectrum selected around fitted lines 124 | spectrum_fit: fit to the spectrum (sum of continuum and Gaussians) 125 | list_names: list of names of the lines that were fit 126 | line_latex: list of latex formatted line names to overplot 127 | line_wls: list of the wavelengths of the overplotted lines 128 | fitted_wl: wavelength(s) of the fitted line 129 | inspect: do you want to inspect the data dynamically? then it plots the 130 | images with show(); otherwise it prints out the figure into a 131 | png file 132 | plot_fit: do you want to plot only the observed spectrum or overplot the 133 | fit to the spectrum 134 | cont_width: wavelength to the left and right of the line that we will plot 135 | rest_spectral_resolution: restframed FWHM of the instrument 136 | sky: sky regions to be masked 137 | Output: 138 | Figure in show() or a saved figure in an external png file 139 | """ 140 | # Set the title to the plot 141 | title = ( 142 | f"{target['Sample']}\t".replace("_", "\_") 143 | + target["Pointing"].replace("_", "\_") 144 | + f"\t{target['SourceNumber']}\tz={float(target['Redshift']):.3}".replace( 145 | "_", "\_" 146 | ) 147 | ) 148 | 149 | # Set the basename name of the png outfile 150 | basename = rf.naming_convention( 151 | data_path, 152 | target["Sample"], 153 | target["SourceNumber"], 154 | target["Setup"], 155 | target["Pointing"], 156 | "linefits", 157 | ) 158 | for i in range(len(line_names)): 159 | basename = f"{basename}.{line_names[i].strip()}" 160 | 161 | # Set up figure and axes 162 | fig = plt.figure(211) 163 | ax = fig.add_subplot(111) 164 | 165 | # Overplot the observed spectrum 166 | ax.plot(spectrum["wl_rest"], spectrum["flux_rest"], color="gray") 167 | # Overplot the observed spectrum around the emission line in a bold colour 168 | ax.plot(spectrum_line["wl_rest"], spectrum_line["flux_rest"], color="black") 169 | 170 | # plot zoom in on the fitted line 171 | sub_axes = inset_axes( 172 | ax, 173 | width="100%", 174 | height="100%", 175 | loc="upper left", 176 | bbox_to_anchor=(0.5, 1 - 0.35, 0.3, 0.3), 177 | bbox_transform=ax.transAxes, 178 | ) 179 | 180 | # Select the region around the emission line and overplot the line 181 | select = (spectrum["wl_rest"] < (np.average(fitted_wl) + cont_width)) & ( 182 | spectrum["wl_rest"] > (np.average(fitted_wl) - cont_width) 183 | ) 184 | sub_axes.plot(spectrum["wl_rest"][select], spectrum["flux_rest"][select], color="gray") 185 | ax.set_xlim( 186 | min(spectrum["wl_rest"]) - cont_width, max(spectrum["wl_rest"]) + cont_width 187 | ) 188 | ylims = list(ax.get_ylim()) 189 | ylims[0] = max(ylims[0], -2 * np.std(spectrum["flux_rest"].value)) 190 | ax.set_ylim(ylims) 191 | sub_axes.set_xlim(sub_axes.get_xlim()) 192 | sub_axes.set_ylim(sub_axes.get_ylim()) 193 | 194 | # Mark the emission line 195 | for line in fitted_wl: 196 | sub_axes.axvline(x=line, color="k", linestyle="-") 197 | 198 | plot_gaussian_fit( 199 | spectrum["wl_rest"][select], spectrum_fit, ax, rest_spectral_resolution 200 | ) 201 | plot_gaussian_fit( 202 | spectrum["wl_rest"][select], spectrum_fit, sub_axes, rest_spectral_resolution 203 | ) 204 | 205 | # Overplot the emission lines of reference 206 | ax2 = overplot_lines(ax, line_latex, line_wls) 207 | 208 | # Overplot sky bands 209 | overplot_sky(ax, target["Redshift"], sky) 210 | overplot_sky(sub_axes, target["Redshift"], sky) 211 | 212 | sub_axes.set_zorder(ax.get_zorder() + 1) # put ax in front of ax2 213 | ax.patch.set_visible(False) # hide the 'canvas' 214 | sub_axes.patch.set_visible(True) 215 | 216 | # Add title to the zoomed in axis 217 | sub_axes.set_title(f"{title}") 218 | ax.set_xlabel( 219 | r"$\boldsymbol{\lambda}$ " 220 | + f"({spectrum['wl_rest'].unit.to_string('latex_inline')})", 221 | ) 222 | ax.set_ylabel( 223 | r"$F_{\boldsymbol{\lambda}}$" 224 | + f"({spectrum['flux'].unit.to_string('latex_inline')})", 225 | ) 226 | 227 | # show or save the figure depending on the command line flag 228 | if inspect: 229 | fig.show() 230 | # While not keyboard press, i.e. while it is a mouse press go back into 231 | # the loop until you actually do press a key 232 | while not plt.waitforbuttonpress(): 233 | pass 234 | if not inspect: 235 | fig.savefig(f"{basename}.png", format="png", bbox_inches="tight") 236 | fig.clf() 237 | plt.close(fig) 238 | gc.collect() 239 | 240 | 241 | @contextmanager 242 | def overview_plot( 243 | target, data_path, line_groups, spectrum, cont_width, rest_spectral_resolution, sky 244 | ): 245 | """ 246 | Overview plot of the spectrum of a single target, with zoom-in plots around 247 | the lines that were fit. The zoom-in plots have the individual Gaussian fits 248 | of the lines overplotted. The lines grouped together are also plotted 249 | together. 250 | Input: 251 | target: properties of the target as read in from rf.read_lof 252 | data_path: place where the plot will be saved 253 | line_groups: how many groups of lines are there, i.e. how many zoom-in 254 | plots do we want 255 | spectrum: spectrum of the source 256 | cont_width: wavelength to the left and right of the line that we will plot 257 | rest_spectral_resolution: restframed FWHM of the instrument 258 | sky: sky regions to be masked 259 | """ 260 | 261 | # Generate the title of the plot from information on the target 262 | title = ( 263 | f"{target['Sample']}\t".replace("_", "\_") 264 | + target["Pointing"].replace("_", "\_") 265 | + f"\t{target['SourceNumber']}\tz={float(target['Redshift']):.3}".replace( 266 | "_", "\_" 267 | ) 268 | ) 269 | # Basename that will be used in the savefile name 270 | basename = rf.naming_convention( 271 | data_path, 272 | target["Sample"], 273 | target["SourceNumber"], 274 | target["Setup"], 275 | target["Pointing"], 276 | "linefits", 277 | ) 278 | Myxlabel = ( 279 | r"$\boldsymbol{\lambda}$ " 280 | + f"({spectrum['wl_rest'].unit.to_string('latex_inline')})" 281 | ) 282 | Myylabel = ( 283 | r"$F_{\boldsymbol{\lambda}}$ " 284 | + f"({spectrum['flux'].unit.to_string('latex_inline')})" 285 | ) 286 | # How many zoom-in plots will we have? As many as groups of lines, as the 287 | # lines grouped together will also be plotted together 288 | No_plots = len(line_groups) 289 | 290 | # Set figure and add axis 291 | fig = plt.figure() 292 | 293 | ax = fig.add_subplot(311) 294 | 295 | # Plot the base restframe spectrum to the main axis 296 | ax.plot(spectrum["wl_rest"], spectrum["flux_rest"], color="k") 297 | 298 | # Set x & y axis limits based on the spectrum 299 | ax.set_xlim( 300 | min(spectrum["wl_rest"]) - cont_width, max(spectrum["wl_rest"]) + cont_width 301 | ) 302 | ylims = list(ax.get_ylim()) 303 | ylims[0] = max(ylims[0], -2 * np.std(spectrum["flux_rest"].value)) 304 | ax.set_ylim(ylims) 305 | 306 | j = 0 307 | 308 | # Function to dynamically add the fits for each emission lines as they are 309 | # produced/come from a loop 310 | def plot_line(line, spectrum_fit, rest_spectral_resolution, sky): 311 | # Counter j to keep track in which subplot we should plot the emission 312 | # line 313 | nonlocal j 314 | 315 | # Select a small wavelength range around the emission line where to 316 | # overplot the fit 317 | select = ( 318 | spectrum["wl_rest"] < (np.average(line["wavelength"]) + cont_width) 319 | ) & (spectrum["wl_rest"] > (np.average(line["wavelength"]) - cont_width)) 320 | 321 | # Create a zoomed in axis focusing on each line 322 | # Semi-random x-y ratio that does not match the main plot, but better 323 | # shows the lines 324 | 325 | Zoom = namedtuple("Zoom", ["x", "y"]) 326 | zoom = Zoom(x=0.9 / No_plots, y=1.5) 327 | axins = inset_axes( 328 | ax, 329 | f"{100*zoom.x}%", 330 | f"{100*zoom.y}%", 331 | loc="lower left", 332 | bbox_to_anchor=(0 + (0 if No_plots < 2 else j / (No_plots - 1)), 1.3, 1, 1), 333 | bbox_transform=ax.transAxes, 334 | ) 335 | 336 | # Hide axis labels for inset axes 337 | axins.xaxis.label.set_visible(False) 338 | if j > 0: 339 | axins.yaxis.label.set_visible(False) 340 | # Plot the observed spectrum in the zoomed-in axis 341 | axins.plot(spectrum["wl_rest"][select], spectrum["flux_rest"][select], color="k") 342 | 343 | # Overplot the gaussian fit to the line in the zoomed-in axis 344 | plot_gaussian_fit( 345 | spectrum["wl_rest"][select], spectrum_fit, axins, rest_spectral_resolution 346 | ) 347 | axins.set_xlim( 348 | [ 349 | np.average(line["wavelength"]) - cont_width, 350 | np.average(line["wavelength"]) + cont_width, 351 | ] 352 | ) 353 | axins.set_ylim(ylims) 354 | 355 | # Mark the emission lines fitted 356 | [axins.axvline(x=l["wavelength"], color="gray", linestyle="-") for l in line] 357 | texts = [ 358 | axins.text( 359 | l["wavelength"], 360 | axins.get_ylim()[1] * 1.01, 361 | f"{l['latex']}", 362 | ha="center", 363 | va="bottom", 364 | ) 365 | for l in line 366 | ] 367 | 368 | # Adjust for potential label overlap 369 | if len(texts) > 1: 370 | adjust_labels(texts, fig, axins, zoom) 371 | 372 | # if it's the first plot, i.e. the left-most plot, label the y axis 373 | if j == 0: 374 | axins.set_ylabel(Myylabel) 375 | 376 | # Overplot sky 377 | overplot_sky(axins, target["Redshift"], sky) 378 | 379 | mark_inset(ax, axins, loc1a=4, loc1b=1, loc2a=3, loc2b=2, fc="none", ec="0.5") 380 | 381 | j = j + 1 382 | 383 | # Return the individual lines plots to the overview plot 384 | yield plot_line 385 | 386 | # Set title, taking into account inset axes, if they exist 387 | if j == 0: 388 | ax.set_title(f"{title}") 389 | else: 390 | ax.set_title(f"{title}", y=3) 391 | 392 | # Mark atmosphere absorption 393 | overplot_sky(ax, target["Redshift"], sky) 394 | 395 | ax.set_ylim(ylims) 396 | 397 | # Set the proper labels 398 | ax.set_xlabel(Myxlabel) 399 | ax.set_ylabel(Myylabel) 400 | # Save the figure under the above-decided name 401 | plt.savefig(f"{basename}.png", format="png", bbox_inches="tight") 402 | fig.clf() 403 | plt.close() 404 | gc.collect() 405 | 406 | 407 | def plot_gaussian_fit(wl, spectrum_fit, ax, rest_spectral_resolution): 408 | """ 409 | Plot the a line fit as a continuum + a Gaussian, whenever the line was 410 | detected. Plot a dashed line for upper limits. 411 | """ 412 | for line_fit in spectrum_fit.lines: 413 | # Plot detections 414 | if isinstance(line_fit, gf.Line): 415 | gauss_part = gf.gauss_function( 416 | wl, 417 | line_fit.height.value, 418 | line_fit.wavelength.value, 419 | line_fit.sigma.value, 420 | ) 421 | fit_flux = gauss_part + spectrum_fit.continuum.value 422 | ax.plot(wl, fit_flux) 423 | # Plot upper limits 424 | if isinstance(line_fit, gf.NonDetection) & ( 425 | not isinstance(line_fit, gf.NoCoverage) 426 | ): 427 | s = so.fwhm_to_sigma(rest_spectral_resolution) 428 | gauss_part = gf.gauss_function( 429 | wl, so.amplitude_to_height(line_fit.amplitude, s), line_fit.restwl, s 430 | ) 431 | fit_flux = gauss_part + spectrum_fit.continuum.value 432 | ax.plot(wl, fit_flux, linestyle="--") 433 | 434 | 435 | def adjust_labels(texts, fig, ax, zoom=None): 436 | """ 437 | Sometime added text labels for lines overlap because the lines are too close. 438 | This attempts to move the labels horizontally to allow for better visibility. 439 | ! Changes are made only for 2 and 3 labels, not more. 440 | Input: 441 | texts: lists of text that need to be shuffled around 442 | fig: parent figure 443 | ax: parent axis 444 | zoom: if any zoom was added to the axis with respect to the parent axis. 445 | Return: 446 | new positions for text objects. 447 | """ 448 | transf = ax.transData.inverted() 449 | unzoom = [1 / z for z in zoom] 450 | bbox = [ 451 | t.get_window_extent(renderer=fig.canvas.get_renderer()) 452 | .expanded(*(unzoom)) 453 | .transformed(transf) 454 | for t in texts 455 | ] 456 | 457 | # Calculate the offset between the right side of the current box and 458 | # compare it to the the left side of the next box 459 | difs = [bbox[i].x1 - bbox[i + 1].x0 for i in range(len(bbox) - 1)] 460 | 461 | # Make changes only if there is overlap between text boxes 462 | if any(dif > 0 for dif in difs): 463 | if len(texts) == 2: 464 | offsets = ((-difs[0] / 2), (difs[0] / 2)) 465 | if len(texts) == 3: 466 | offsets = ((-max(difs[0], 0)), (0), (max(difs[1], 0))) 467 | for (text, offset) in zip(texts, offsets): 468 | text.set_x(text.get_position()[0].value + offset) 469 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gleam 2 | Galaxy Line Emission & Absorption Modeling 3 | 4 | Citation: [Andra Stroe and Victor-Nicolae Savu 2021 **AJ** 161 158](https://iopscience.iop.org/article/10.3847/1538-3881/abe12a) 5 | 6 | [![DOI](https://zenodo.org/badge/230828355.svg)](https://zenodo.org/badge/latestdoi/230828355) 7 | 8 | ## What is gleam? 9 | 10 | **gleam** is a Python package for fitting Gaussian models to emission and absorption lines in large samples of 1D galaxy spectra. **gleam** is tailored to work well without much human interaction on optical and infrared spectra in a wide range of instrument setups and signal-to-noise regimes. **gleam** will create a fits table with Gaussian line measurements, including central wavelength, 11 | width, height and amplitude, as well as estimates for the continuum under the line and the line flux, luminosity, equivalent width and velocity width. **gleam** will also, optionally, make plots of the spectrum with fitted lines overlaid. 12 | 13 | ## Features 14 | 15 | - Process large numbers of sources in batch mode. 16 | - Jointly fit lines located close together, report upper limits and identify lines without spectral coverage. 17 | - A single configuration file for an entire project, which you can use to customize the fitting for specific telescope/instrument configurations or even single sources. 18 | - Human-readable YAML configuration file, in which units can be specified. 19 | - Plots of an entire spectrum with the line fits, as well as plots for each individual line fitted. 20 | - Well integrated with Astropy, which enables the use of units and fits tables. 21 | - Uses LMFIT to perform the fitting and report errors on fit parameters. 22 | - Simple installation with pip. 23 | 24 | ## How to run and output 25 | 26 | **gleam** fits lines in 1D spectra using redshift information from a metadata file and several other parameters from a central configuration file. 27 | 28 | To run **gleam**, the following are needed: 29 | - A set of 1D spectra (optical convention, λ versus Fλ), in fits table format 30 | - A set of metadata files, in fits table or ASCII format, where details of each source are listed 31 | - A configuration file, in YAML format, to specify line lists and fitting constraints 32 | - A line catalog, in fits table format 33 | - (Optional) A sky absorption/emission catalog, in fits table format 34 | 35 | Details on the input files can be found further down. 36 | 37 | The outputs of **gleam** include: 38 | - A fits table with line measurements for each source 39 | - (Optional) Plots of the spectrum with overplotted line fits and upper limits. 40 | 41 | To run the **gleam** using the defaults, you can type in the terminal: 42 | ``` 43 | gleam 44 | ``` 45 | 46 | **gleam** has a number of optional command line arguments. For details type: 47 | ``` 48 | gleam --help 49 | ``` 50 | 51 | An example dataset is contained within the git repository. To download it, 52 | either use the download button or in the terminal: 53 | ``` 54 | wget https://github.com/multiwavelength/gleam/raw/main/example.tar.gz 55 | ``` 56 | 57 | ## Input data and configuration file 58 | 59 | ### The input spectra 60 | The input spectra should be in fits format, ideally with units in the headers. They should contain 3 columns: the observed wavelength, the flux and the error, as follow: 61 | 62 | | wl|flux|stdev| 63 | |-|-|-| 64 | | 8972.34| 0.1| 0.01| 65 | |⋮|⋮|⋮| 66 | 67 | *Note*: **gleam** assumes that the spectrum in given in the optical convention, i.e. wavelength (λ) versus flux density (Fλ). If the spectral axis is given in frequencies (ν), or if the y axis is given in λFλ, Fν, or νFν, **gleam** will not produce the intended results. 68 | 69 | In order to identify source across the spectra and the metadata files, a naming convention needs to be followed: 70 | - `spec1d.Sample.Setup.Pointing.SourceNumber.fits` 71 | 72 | ### Metadata file 73 | The metadata file contains information about individual sources in the project, such as the setup and pointing they were observed with, the source number to identify them and their redshift. The metadata file is used to pull information about each source. You can have a single metadata file or multiple ones, as long as sources are unique between them. 74 | 75 | The metadata file can be in fits format or ASCII format (with commented header), but should contain the following columns: 76 | 77 | | Setup | Pointing | SourceNumber | Sample | Redshift | 78 | |-------|----------|--------------|--------|----------| 79 | | Keck | P1 | 123 | Cosmos | 1.2303 | 80 | |⋮|⋮ |⋮ |⋮|⋮ | 81 | 82 | Column descriptions: 83 | - **Setup**: the telescope, instrument or mode the source was observed with (must match with setup in the spectrum name) 84 | - **Pointing**: usually multiple pointings or configurations are observed (must match with pointing in the spectrum name), 85 | - **SourceNumber**: a unique identifier for the source, within a single setup+pointing combination (must match with source number in the spectrum name), 86 | - **Sample**: parent sample for the source, e.g. if part of a single galaxy cluster or a famous field (must match with sample in the spectrum name), 87 | - **Redshift**: redshift for the source, ideally correct to within 0.0001. 88 | 89 | Metadata files must start with "meta.": 90 | - `meta.Sample.Setup.Pointing.fits` 91 | - `meta.Sample.Setup.Pointing.dat` 92 | - `meta.fits` 93 | 94 | ### Configuration file 95 | 96 | The configuration file enables the user to customize the fitting at 4 levels: use the **gleam** defaults as much as possible, use a global set of parameters for an entire project, specify a number of telescope/instrument specific overrides or even specify overrides for individual sources. 97 | 98 | A minimal working configuration example: 99 | ```yaml 100 | globals: 101 | line_table: line_lists/Main_optical_lines.fits 102 | resolution: 4.4 Angstrom 103 | ``` 104 | 105 | To report luminosities based on the fitted models, **gleam** uses Hubble 106 | constant, Omega0 and the CMB temperature. While these parameters are reasonably 107 | accurate and up to date, your project may require slightly different values. You 108 | can use the `cosmology` section to override one or more of these parameters. 109 | The same cosmology is going to be used consistently across all the spectra 110 | within a project. 111 | 112 | Here is another full example of a configuration file demonstrating how you could 113 | define custom cosmological parameters for your project (the values here happen 114 | correspond to the defaults): 115 | 116 | ```yaml 117 | globals: 118 | line_table: line_lists/Main_optical_lines.fits 119 | resolution: 4.4 Angstrom 120 | 121 | cosmology: 122 | H0: 75 km / (Mpc s) 123 | Om0: 0.3 124 | Tcmb0: 2.725 K 125 | ``` 126 | 127 | The configuration has a few more fully customizable parameters, related to 128 | model fitting, line selection and sky absorption masking. 129 | 130 | With **gleam**, you can analyze large numbers of spectra in a uniform manner, 131 | even with data taken in different conditions, with different instruments on 132 | different telescopes and for a wide variety of sources. To make it easy to 133 | capture the specifics of each spectrum, **gleam** offers you the possibility to 134 | specify parameters at three different levels. 135 | 1. The global level (`globals`) allows you to override the default configuration 136 | for all the spectra. This is the most coarse level of customization while the 137 | next levels provide more fine-grained overrides. 138 | 2. The setup level offers a way to apply configuration overrides to 139 | groups of spectra (named `setups`). Each spectrum belongs to exactly one 140 | setup. What setups mean is entirely up to you. In general, you would use this 141 | level to capture differences between telescopes or instruments, such as the 142 | spectral resolution. The configuration parameters specified at this level 143 | supersede the the global configuration and the built-in defaults. Note that 144 | the setup name needs to match that in the corresponding sources. 145 | 3. The per-source level (named `sources`) allows you to customize the parameters for each and 146 | every source. While this can be very helpful to account for some particularly 147 | troublesome cases, it should be used sporadically both due to the associated 148 | typing burden as well as in the spirit of keeping the results comparable. The 149 | naming convention of the source should be in line with the input spectrum 150 | file, without the 'spec1d' and '.fits'. For example: 151 | - Example source within sources: "Sample.Setup.Pointing.SourceNumber" 152 | 153 | 154 | The full structure of the configuration file is: 155 | 156 | ```yaml 157 | 158 | globals: 159 | ... 160 | setups: 161 | : 162 | ... 163 | : 164 | ... 165 | sources: 166 | : 167 | ... 168 | cosmology: 169 | H0: 75 km / (Mpc s) 170 | Om0: 0.3 171 | Tcmb0: 2.725 K 172 | ``` 173 | 174 | The parameters for each spectrum will be computed by stacking the applicable 175 | overrides on top of the default in order: first the global overrides, then the 176 | applicable per-setup overrides (if any) and finally the applicable per-source 177 | overrides. 178 | 179 | 180 | #### Overrides 181 | 182 | Here are the parameters that can be overridden at either the global, setup or 183 | source level. 184 | 185 | ##### Sky bands to be masked 186 | 187 | There are two parameters that you can use to control whether the fitting should 188 | ignore portions of the spectrum where sky bands may not have been reliably 189 | subtracted. 190 | 191 | First is the path to a catalog which defines the wavelength intervals of sky 192 | bands. The file it points to must be in the fits file format. See below for 193 | exact details of how to create this file. 194 | 195 | ```yaml 196 | sky: line_lists/Sky_bands.fits 197 | ``` 198 | 199 | The second is a flag that enables or disables the masking of all the sky bands 200 | in the spectrum. 201 | 202 | ```yaml 203 | mask_sky: True 204 | ``` 205 | 206 | By default (i.e. if no `sky` or `mask_sky` overrides are applied to a source), 207 | there is no sky masking and the entire spectrum is used. In order for masking to 208 | take place, `sky` must be set appropriately and `mask_sky` must be set to `True`. 209 | 210 | Note that the two overrides don't need to be specified at the same level. For 211 | example, you might want to specify `sky` at the `global` level and then just set 212 | `mask_sky` to `True` for individual `sources` (or `setups`) for which the sky 213 | subtraction is inadequate. 214 | 215 | ##### Emission/absorption lines to fit 216 | 217 | By default, it will fit all lines listed in the line table. Otherwise, only 218 | the lines specified under "lines" will be fitted. One needs to use the same 219 | names for the lines as in the line table. 220 | 221 | ```yaml 222 | line_table: line_lists/Main_optical_lines.fits # line catalog 223 | lines: # names of line to select from line table 224 | - Hb 225 | - OIII4 226 | - OIII5 227 | - OII 228 | - Ha 229 | - NII1 230 | - SII1 231 | - SII2 232 | ``` 233 | 234 | 235 | ##### Fitting parameters 236 | 237 | There are a number of parameters that each affect the way the line models are 238 | fit to the data. To distinguish them visually in the configuration file, they 239 | are grouped under the `fitting` field. However, they can be individually 240 | overridden at any of the three levels (global, per-setup and per-source). 241 | 242 | ```yaml 243 | fitting: 244 | # Signal to noise limit for a detection 245 | SN_limit: 2 246 | # Tolerance for considering emission lines part of a group and thus fitting them together 247 | tolerance: 26.0 Angstrom 248 | # Range around either side of the Gaussian center to be probed 249 | w: 3.0 Angstrom 250 | # Wavelength range used to mask lines 251 | mask_width: 20.0 Angstrom 252 | # Range used for selecting continuum left and right of the source 253 | cont_width: 70.0 Angstrom 254 | # Constraints on the center of each gaussian. The options are: 255 | # - free: (default) the center can be anywhere within the fitting range 256 | # - constrained: the center must be within a distance of `w` from the expected 257 | # position specified in the `line_table` 258 | # - fixed: the center is fixed to the expected position of the corresponding 259 | # line as specified in the `line_table` 260 | center: constrained 261 | ``` 262 | 263 | ### Line catalog 264 | 265 | The line catalog contains a list of lines to draw from when fitting. 266 | It should be in the fits format (preferably with units) and contain columns for 267 | the line name, wavelength and (optionally) the LaTeX representation of the line 268 | name (which is only used when plotting). 269 | 270 | |line|wavelength|latex| 271 | |-|-|-| 272 | |Ha | 6564.614 | H$\boldsymbol{\alpha}$ | 273 | |NII1 | 6585.27 | [N{\sc ii}] | 274 | |⋮|⋮| ⋮ | 275 | 276 | A subset of the lines can be specified in the configuration file, otherwise 277 | the entire list of lines from the catalog will be used for fitting. 278 | 279 | ### Sky catalog 280 | 281 | In cases where the sky correction is not done perfectly, your data may still be affected by sky absorption or emission. You can specify a list of bands (in observed wavelength units) to avoid. These bands will be masked and disregarded for fitting and treated as if no spectral coverage is available. (i.e. no upper limits will be reported). Masking of the sky can be turned on and off in the config file. 282 | 283 | The sky catalog must be in the fits format (preferably with units) and contain 284 | the following columns: 285 | 286 | | band | wavelength_min | wavelength_max| 287 | |-|-|-| 288 | | Aband | 7586.0 | 7658.0 | 289 | | Bband | 6864.0 | 6945.0 | 290 | |⋮|⋮|⋮| 291 | 292 | ## Output 293 | 294 | ### Line fits tables 295 | For each of the sources in your sample, **gleam** will produce a table with all of the line fits and upper limits (if possible with units derived from the input data). Each line fitted is represented in a separate row, with all the corresponding line fit details contained in different column. The table contains information from the expected wavelength of the line and the redshift of the source, to emission line fit parameters, line fluxes and equivalent widths. 296 | 297 | All of the output files will start with "linefits" and follow the naming convention described above. 298 | - Line fits table: "linefits.Sample.Setup.Pointing.SourceNumber.fits" 299 | 300 | An example of the header of such a table and a description of the columns can be found below. Since there are many column, they are listed here in multiple groups. 301 | 302 | | line| wavelength | latex | z | zline | zline_err | 303 | |- |- |- |- |- |- | 304 | Ha | 6564.614 | H$\boldsymbol{\alpha}$ | 0.26284 | 0.2629235| 3.3654374E-5 | 305 | 306 | | zoffset | zoffset_err | cont | cont_err | wl | wl_err | 307 | |- |- |- |- |- |- | 308 | |8.3521445E-5 | 3.3654374E-5 | 0.0012948853 | 1.11991896E-4 | 6565.162 | 0.22092798 | 309 | 310 | | height | height_err | sigma | sigma_err | amplitude |amplitude_err| 311 | |- |- |- |- |- |- | 312 | | 0.017354544 | 0.001047185 | 2.9371586 |0.2215329 | 0.12777047 | 0.008491282 | 313 | 314 | | flux | flux_err | luminosity | luminosity_err | EWrest | EWrest_err| 315 | |- |- |- |- |- |- | 316 | | 0.12777047 | 0.008491282 | 0.27209833 | 0.018082924 | 98.673195 | 10.762495 | 317 | 318 | | FWHM| FWHM_err| v | v_err | detected |covered | 319 | |- |- |- |- |- |- | 320 | | 6.9164796 | 0.5216701 | 118.18059 | 23.823605 | true | true| 321 | 322 | A description of each column: 323 | - **line**: Name of the line, from the input line table. 324 | - **wavelength**: Rest wavelength of the line. 325 | - **latex**: Name of the line in Latex format. 326 | - **z**: Redshift of the source, from the input metadata file. 327 | - **zline, zline_err**: Redshift derived from this particular line and its error. 328 | - **zoffset, zoffset_err**: Offset between the systemic source redshift and this line. 329 | - **cont, cont_err**: Continuum estimation around the line and its error. 330 | - **wl, wl_err**: Central wavelength of the line and its error, estimated from the Gaussian fit. 331 | - **height, height_err**: Height of the line and its error, estimated from the Gaussian fit. 332 | - **sigma, sigma_err**: Sigma of the line and its error, estimated from the Gaussian fit. 333 | - **amplitude, amplitude_err**: Amplitude of the line and its error, estimated from the Gaussian fit. 334 | - **flux, flux_err**: Flux of the line and its error. 335 | - **luminosity, luminosity_err**: Luminosity of the line and its error. 336 | - **EWrest, EWrest_err**: Rest-frame equivalent width of the line and its error. 337 | - **FWHM, FWHM_err**: Deconvolved full-width-at-half-maximum width, in wavelength units and its error. 338 | - **v, v_err**: Deconvolved full-width-at-half-maximum velocity width, in wavelength units and its error. 339 | - **detected**: True if line is detected, False if non-detection. 340 | - **covered**: True if line is covered by the spectrum. False is coverage is missing at the location of the lines, i.e. fits or upper limits are not possible. 341 | 342 | If the input spectrum has units, the line parameters will also be reported with units. 343 | When the spectral line is not covered by the spectrum, fit values and errors are omitted. 344 | If a line is not detected, **gleam** only reports an upper limit in the amplitude column and omits all other parameters. 345 | The FWHM and the velocity are only reported if the line is spectrally resolved. 346 | 347 | ### Plots 348 | 349 | If plotting is enabled, **gleam** produces two types of figures: a figure showing the entire spectrum with zoom-ins on the emission line fits. The second type of plots are focused on each line fit. Areas masked by sky are shaded gray for clarity. 350 | 351 | - Spectrum plot with the fitted lines overlaid: "linefits.Sample.Setup.Pointing.SourceNumber.png" 352 | - Spectrum plot zooming in on Halpha and NII1: "linefits.Sample.Setup.Pointing.SourceNumber.Ha.NII1.png" 353 | 354 | *NOTE*: Plotting high quality figures makes **gleam** very slow (a factor of at least 15 slower than without it). Matplotlib with Latex has some memory leak issues, which can cause **gleam** to slowly consume all the memory. I recommend avoiding batch processing 355 | more than 500 sources when also creating plots. 356 | 357 | ## Installation requirements 358 | 359 | - Python ^3.8 360 | - pip ^20.0 361 | - (Optional, but recommended) Latex, when plotting. 362 | 363 | ## How to install 364 | ### From pypi 365 | The recommended way to get `gleam` is from the Python Package Index (PyPI). The package is named `astro-gleam` on PyPI: 366 | 367 | ``` 368 | pip install astro-gleam 369 | ``` 370 | 371 | ### From source 372 | In rare cases, when you need to install a version that is not published on PyPI, you can install `gleam` directly from the source repository: 373 | 374 | ``` 375 | pip install git+https://github.com/multiwavelength/gleam 376 | ``` 377 | 378 | You can learn about what options are available when installing from source by reading the [official documentation](https://packaging.python.org/tutorials/installing-packages/#installing-from-vcs). 379 | 380 | ## Troubleshooting 381 | 382 | ### Missing Python C headers on Linux 383 | 384 | If you get an error that contains the following message, it means that your Linux system is missing the C libraries for your version of python. 385 | 386 | ``` 387 | fatal error: Python.h: No such file or directory 388 | 4 | #include "Python.h" 389 | | ^~~~~~~~~~ 390 | compilation terminated. 391 | ``` 392 | 393 | To fix the issue, you need to install the dev package for your version of python. Different Linux distributions have slight differences in naming, but here is what the command would look like on a Debian-based distribution (e.g. Ubuntu, Mint) for python 3.10: 394 | 395 | ``` 396 | sudo apt install libpython3.10-dev 397 | ``` 398 | 399 | After this, the command to install gleam should work. 400 | 401 | ## How to cite **gleam** 402 | 403 | If you use **gleam** in your published projects or as a dependency in your code, please include a citation to the companion paper in the Astronomical Journal, as well as a citation to the repository through Zenodo: 404 | 405 | Citation: [Andra Stroe and Victor-Nicolae Savu 2021 **AJ** 161 158](https://iopscience.iop.org/article/10.3847/1538-3881/abe12a) 406 | 407 | [![DOI](https://zenodo.org/badge/230828355.svg)](https://zenodo.org/badge/latestdoi/230828355) 408 | -------------------------------------------------------------------------------- /gleam/gaussian_fitting.py: -------------------------------------------------------------------------------- 1 | __author__ = "Andra Stroe" 2 | __version__ = "0.1" 3 | 4 | import sys 5 | from typing import List, Union 6 | from dataclasses import dataclass 7 | import itertools 8 | 9 | import numpy as np 10 | from astropy import units as u 11 | from astropy import constants as const 12 | from astropy.table import QTable, Column, hstack 13 | from astropy.units.quantity import Quantity as Qty 14 | from colorama import Fore 15 | from astropy.cosmology import FlatLambdaCDM 16 | 17 | from lmfit.models import GaussianModel, ConstantModel 18 | from lmfit.model import ModelResult 19 | 20 | import gleam.spectra_operations as so 21 | from gleam.constants import Length 22 | 23 | 24 | @dataclass 25 | class RandomVariable: 26 | value: Qty 27 | error: Qty 28 | 29 | @staticmethod 30 | def from_param(param): 31 | return RandomVariable(value=param.value, error=param.stderr) 32 | 33 | @property 34 | def badness(self): 35 | return self.error / self.value 36 | 37 | @property 38 | def significance(self): 39 | return None if self.error is None else abs(self.value / self.error) 40 | 41 | def __mul__(self, other): 42 | """ self * other """ 43 | return RandomVariable(value=self.value * other, error=self.error * other) 44 | 45 | def __rmul__(self, other): 46 | """ other * self """ 47 | return self * other 48 | 49 | 50 | @dataclass 51 | class Line: 52 | wavelength: RandomVariable 53 | height: RandomVariable 54 | sigma: RandomVariable 55 | amplitude: RandomVariable 56 | continuum: RandomVariable 57 | fwhm: RandomVariable 58 | restwl: float 59 | z: float 60 | cosmo: FlatLambdaCDM 61 | resolution: Length 62 | 63 | @property 64 | def flux(self): 65 | # Line flux 66 | return RandomVariable(value=self.amplitude.value, error=self.amplitude.error,) 67 | 68 | @property 69 | def luminosity(self): 70 | # Line luminosity 71 | ld = self.cosmo.luminosity_distance(self.z).to(10 ** 28 * u.cm) 72 | v = 4.0 * np.pi * ld ** 2 * self.flux.value 73 | e = 4.0 * np.pi * ld ** 2 * self.flux.error 74 | return RandomVariable(v, e) 75 | 76 | @property 77 | def ew_rest(self): 78 | # Equivalent width of the emission line in the restframe 79 | v = self.amplitude.value / self.continuum.value 80 | e = np.abs(v) * np.sqrt( 81 | (self.continuum.error / self.continuum.value) ** 2 82 | + (self.amplitude.error / self.amplitude.value) ** 2 83 | ) 84 | return RandomVariable(value=v, error=e) 85 | 86 | @property 87 | def z_offset(self): 88 | # Offset in the redshift calculated from the offset of the absolute 89 | # wavelength of the line compare to the one measured from the restframe 90 | # spectrum; Could be caused by imperfect redshift estimation (in case it 91 | # is systematically measured in more emission lines) or could be velocity 92 | # offset because of out/inflows 93 | v = self.wavelength.value / self.restwl - 1.0 94 | e = self.wavelength.error / self.restwl 95 | return RandomVariable(value=v, error=e) 96 | 97 | @property 98 | def z_line(self): 99 | # Redshift calculated from the offset of the absolute wavelength of the 100 | # line compare to the one measured from the restframe spectrum; Could 101 | # be caused by imperfect redshift estimation (in case it is 102 | # systematically measured in more emission lines) or could be velocity 103 | # offset because of out/inflows 104 | v = self.z_offset.value + self.z 105 | e = self.z_offset.error 106 | return RandomVariable(value=v, error=e) 107 | 108 | @property 109 | def velocity_fwhm(self): 110 | # Deconvolved velocity fwhm 111 | # Observed resolution at the restframe wavelength 112 | if (self.fwhm.value ** 2.0 - self.resolution ** 2.0) < 0: 113 | return RandomVariable(value=np.nan * u.km / u.s, error=np.nan * u.km / u.s) 114 | else: 115 | eff_fwhm = np.sqrt(self.fwhm.value ** 2.0 - self.resolution ** 2.0) 116 | v = const.c.to("km/s") * eff_fwhm / self.restwl 117 | e = const.c.to("km/s") * self.fwhm.error / self.restwl 118 | return RandomVariable(value=v, error=e) 119 | 120 | def as_fits_table(self, line: QTable) -> QTable: 121 | """ 122 | Writes out the results of all the line fits for a single emission line 123 | (either stand alone or one that was part of a doublet, triplet etc), for 124 | a particular source using astropy 125 | Input: 126 | self: emission line fit parameters 127 | line: lab properties of the fitted emission line 128 | Return: 129 | Fits table with all the lab and measured properties of a particular 130 | emission line 131 | """ 132 | t = QTable() 133 | # Vacuum/laboratory properties of the line 134 | t = hstack([t, line]) 135 | 136 | # Source properties 137 | t["z"] = Column( 138 | [self.z], dtype="f", description="Source redshift, from specpro" 139 | ) 140 | 141 | # Redshift calculated from the line 142 | t["zline"] = Column( 143 | [self.z_line.value.value], 144 | dtype="f", 145 | unit=self.z_line.value.unit, 146 | description="Redshift calculated from this emission line", 147 | ) 148 | t["zline_err"] = Column( 149 | [self.z_line.error.value], 150 | dtype="f", 151 | unit=self.z_line.error.unit, 152 | description="Error on the redshift calculated from this emission line", 153 | ) 154 | # Redshift offset calculated between the line redshift and the redshift from 155 | # specpro 156 | t["zoffset"] = Column( 157 | [self.z_offset.value.value], 158 | dtype="f", 159 | unit=self.z_offset.value.unit, 160 | description="Redshift offset between line and specpro redshift", 161 | ) 162 | t["zoffset_err"] = Column( 163 | [self.z_offset.error.value], 164 | dtype="f", 165 | unit=self.z_offset.error.unit, 166 | description="Error on the redshift offset", 167 | ) 168 | 169 | # Line fits 170 | # Continuum around line 171 | t["cont"] = Column( 172 | [self.continuum.value.value], 173 | dtype="f", 174 | unit=self.continuum.value.unit, 175 | description="Continuum around line, Gaussian fit", 176 | ) 177 | t["cont_err"] = Column( 178 | [self.continuum.error.value], 179 | dtype="f", 180 | unit=self.continuum.error.unit, 181 | description="Error on continuum around line, Gaussian fit", 182 | ) 183 | # Gaussian fit 184 | t["wl"] = Column( 185 | [self.wavelength.value.value], 186 | dtype="f", 187 | unit=self.wavelength.value.unit, 188 | description="Restframe wavelength, Gaussian fit", 189 | ) 190 | t["wl_err"] = Column( 191 | [self.wavelength.error.value], 192 | dtype="f", 193 | unit=self.wavelength.error.unit, 194 | description="Error on restframe wavelength, Gaussian fit", 195 | ) 196 | t["height"] = Column( 197 | [self.height.value.value], 198 | dtype="f", 199 | unit=self.height.value.unit, 200 | description="Height, Gaussian fit", 201 | ) 202 | t["height_err"] = Column( 203 | [self.height.error.value], 204 | dtype="f", 205 | unit=self.height.error.unit, 206 | description="Error on height, Gaussian fit", 207 | ) 208 | t["sigma"] = Column( 209 | [self.sigma.value.value], 210 | dtype="f", 211 | unit=self.sigma.value.unit, 212 | description="Sigma, Gaussian fit", 213 | ) 214 | t["sigma_err"] = Column( 215 | [self.sigma.error.value], 216 | dtype="f", 217 | unit=self.sigma.error.unit, 218 | description="Error on sigma, Gaussian fit", 219 | ) 220 | t["amplitude"] = Column( 221 | [self.amplitude.value.value], 222 | dtype="f", 223 | unit=self.amplitude.value.unit, 224 | description="Amplitude, Gaussian fit", 225 | ) 226 | t["amplitude_err"] = Column( 227 | [self.amplitude.error.value], 228 | dtype="f", 229 | unit=self.amplitude.error.unit, 230 | description="Error on amplitude, Gaussian fit", 231 | ) 232 | # Flux 233 | t["flux"] = Column( 234 | [self.flux.value.value], 235 | dtype="f", 236 | unit=self.flux.value.unit, 237 | description="Line flux, Gaussian fit", 238 | ) 239 | t["flux_err"] = Column( 240 | [self.flux.error.value], 241 | dtype="f", 242 | unit=self.flux.error.unit, 243 | description="Error on line flux, Gaussian fit", 244 | ) 245 | # Luminosity 246 | t["luminosity"] = Column( 247 | [self.luminosity.value.value], 248 | dtype="f", 249 | unit=self.luminosity.value.unit, 250 | description="Line luminosity, Gaussian fit", 251 | ) 252 | t["luminosity_err"] = Column( 253 | [self.luminosity.error.value], 254 | dtype="f", 255 | unit=self.luminosity.error.unit, 256 | description="Error on line luminosity, Gaussian fit", 257 | ) 258 | # Equivalent width 259 | t["EWrest"] = Column( 260 | [self.ew_rest.value.value], 261 | dtype="f", 262 | unit=self.ew_rest.value.unit, 263 | description="Restframe line equivalent width, Gaussian fit", 264 | ) 265 | t["EWrest_err"] = Column( 266 | [self.ew_rest.error.value], 267 | dtype="f", 268 | unit=self.ew_rest.error.unit, 269 | description="Error on the restframe equivalent width, Gaussian fit", 270 | ) 271 | 272 | # FWHM of the line 273 | t["FWHM"] = Column( 274 | [self.fwhm.value.value], 275 | dtype="f", 276 | unit=self.fwhm.value.unit, 277 | description="Restframe FWHM, not deconvolved", 278 | ) 279 | t["FWHM_err"] = Column( 280 | [self.fwhm.error.value], 281 | dtype="f", 282 | unit=self.fwhm.error.unit, 283 | description="Error on the restframe FWHM, not deconvolved", 284 | ) 285 | # Deconvolved velocity fwhm, calculated if possible 286 | t["v"] = Column( 287 | [self.velocity_fwhm.value.value], 288 | dtype="f", 289 | unit=self.velocity_fwhm.value.unit, 290 | description="Restframe, deconvolved velocity FWHM; 0 means line is unresolved", 291 | ) 292 | t["v_err"] = Column( 293 | [self.velocity_fwhm.error.value], 294 | dtype="f", 295 | unit=self.velocity_fwhm.error.unit, 296 | description="Error on the restframe, deconvolved velocity FWHM; 99 means line is unresolved", 297 | ) 298 | 299 | # Detection flag 300 | t["detected"] = Column( 301 | [True], dtype="bool", description="Detected or nondetected line" 302 | ) 303 | 304 | # Coverage flag 305 | t["covered"] = Column( 306 | [True], dtype="bool", description="Does the spectrum cover the line?" 307 | ) 308 | 309 | return t 310 | 311 | 312 | @dataclass 313 | class NonDetection: 314 | amplitude: Qty 315 | continuum: RandomVariable 316 | restwl: float 317 | z: float 318 | cosmo: FlatLambdaCDM 319 | 320 | @property 321 | def flux(self) -> Qty: 322 | # Line flux 323 | return self.amplitude 324 | 325 | @property 326 | def luminosity(self) -> Qty: 327 | # Line luminosity 328 | ld = self.cosmo.luminosity_distance(self.z).to(10 ** 28 * u.cm) 329 | v = 4.0 * np.pi * ld ** 2 * self.flux 330 | return v 331 | 332 | def as_fits_table(self, line: QTable) -> QTable: 333 | """ 334 | Writes out the results for a nondetected emission line within a line fit 335 | (either stand alone or one that was part of a doublet, triplet etc), for 336 | a particular source using astropy 337 | Input: 338 | self: emission line fit parameters 339 | line: lab properties of the fitted emission line 340 | Return: 341 | Fits table with all the lab and measured properties of a particular 342 | emission line 343 | """ 344 | t = QTable() 345 | # Vacuum/laboratory properties of the line 346 | t = hstack([t, line]) 347 | 348 | t["z"] = Column( 349 | [self.z], dtype="f", description="Source redshift, from specpro" 350 | ) 351 | 352 | # Line fits 353 | # Continuum around line 354 | t["cont"] = Column( 355 | [self.continuum.value.value], 356 | dtype="f", 357 | unit=self.continuum.value.unit, 358 | description="Continuum around line, Gaussian fit", 359 | ) 360 | t["cont_err"] = Column( 361 | [self.continuum.error.value], 362 | dtype="f", 363 | unit=self.continuum.error.unit, 364 | description="Error on continuum around line, Gaussian fit", 365 | ) 366 | t["amplitude"] = Column( 367 | [self.amplitude.value], 368 | dtype="f", 369 | unit=self.amplitude.unit, 370 | description="Amplitude, Gaussian fit", 371 | ) 372 | # Flux 373 | t["flux"] = Column( 374 | [self.flux.value], 375 | dtype="f", 376 | unit=self.flux.unit, 377 | description="Line flux, Gaussian fit", 378 | ) 379 | # Luminosity 380 | t["luminosity"] = Column( 381 | [self.luminosity.value], 382 | dtype="f", 383 | unit=self.luminosity.unit, 384 | description="Line luminosity, Gaussian fit", 385 | ) 386 | 387 | # Detection flag 388 | t["detected"] = Column( 389 | [False], dtype="bool", description="Detected or nondetected line" 390 | ) 391 | 392 | # Coverage flag 393 | t["covered"] = Column( 394 | [True], dtype="bool", description="Does the spectrum cover the line?" 395 | ) 396 | return t 397 | 398 | 399 | @dataclass 400 | class NoCoverage: 401 | continuum: RandomVariable 402 | restwl: float 403 | z: float 404 | 405 | def as_fits_table(self, line: QTable) -> QTable: 406 | """ 407 | Writes out the results for an emission line within a multiple line fit 408 | for which there is no spectral coverage 409 | Input: 410 | self: emission line fit parameters 411 | line: lab properties of the fitted emission line 412 | Return: 413 | Fits table with all the lab and measured properties of a particular 414 | emission line 415 | """ 416 | t = QTable() 417 | # Vacuum/laboratory properties of the line 418 | t = hstack([t, line]) 419 | 420 | t["z"] = Column( 421 | [self.z], dtype="f", description="Source redshift, from specpro" 422 | ) 423 | 424 | # Line fits 425 | # Continuum around line 426 | t["cont"] = Column( 427 | [self.continuum.value.value], 428 | dtype="f", 429 | unit=self.continuum.value.unit, 430 | description="Continuum around line, Gaussian fit", 431 | ) 432 | t["cont_err"] = Column( 433 | [self.continuum.error.value], 434 | dtype="f", 435 | unit=self.continuum.error.unit, 436 | description="Error on continuum around line, Gaussian fit", 437 | ) 438 | # Detection flag 439 | t["detected"] = Column( 440 | [False], dtype="bool", description="Detected or nondetected line" 441 | ) 442 | 443 | # Coverage flag 444 | t["covered"] = Column( 445 | [False], dtype="bool", description="Does the spectrum cover the line?" 446 | ) 447 | 448 | return t 449 | 450 | 451 | @dataclass 452 | class Spectrum: 453 | lines: List[Union[Line, NonDetection, NoCoverage]] 454 | continuum: RandomVariable 455 | 456 | 457 | def gauss_function(x, h, x0, sigma): 458 | """ 459 | Returns the 1D Gaussian over a given range 460 | Input: 461 | x: range over which the function will be calculated 462 | h: height of the Gaussian 463 | x0: center on x of the Gaussian 464 | sigma: sigma of the Gaussian 465 | Output: 466 | 1D Gaussian function 467 | """ 468 | return h * np.exp(-((x - x0) ** 2) / (2.0 * sigma ** 2.0)) 469 | 470 | 471 | def upper_limit(y, x, SN_limit, rest_spectral_resolution): 472 | """ 473 | In the case where we do not get a SN*sigma detection, we define a SN*sigma 474 | upper limit as per the formula: 475 | SNlimit *sigma_RMS_channel* sqrt( channel_width * line_width ) 476 | Input: 477 | y: spectrum in units of flux or such 478 | x: wavelength 479 | SN_limit: signal to noise limit for detections 480 | rest_spectral_resolution: restframed FWHM of the instrument 481 | """ 482 | # Get width of a pixel in the region of the spectrum probed; in units of 483 | # Angstrom or such. This is pixel width/dispersion and not the resolution 484 | pixel = so.dispersion(x) 485 | 486 | # Takes SN limit into account 487 | upper_limit = ( 488 | SN_limit * so.spectrum_rms(y) * np.sqrt(pixel * rest_spectral_resolution) 489 | ) 490 | return upper_limit 491 | 492 | 493 | def is_good(model: ModelResult, SN_limit) -> bool: 494 | """ 495 | Test whether the model provided a good fit: i.e. whether all the lines are 496 | detected. If the model has no error bars because of a poor fit or if the 497 | amplitude of the Gaussian is measured at a significance lower than the 498 | indicated limit that would be flagged as a bad fit 499 | """ 500 | if model.result.errorbars == False: 501 | return False 502 | fitparams = model.params 503 | return all( 504 | RandomVariable.from_param(fitparams[f"g{i}_amplitude"]).significance > SN_limit 505 | for i in range(len(model.components) - 1) 506 | ) 507 | 508 | 509 | def subsets(length): 510 | """ 511 | Generate all subsets of the line set, in case we want to fit only 1, 2 or 512 | many subsets of the original line list 513 | """ 514 | for n in range(length, -1, -1): 515 | yield from itertools.combinations(range(length), n) 516 | 517 | 518 | def model_selection( 519 | redshift, 520 | x, 521 | y, 522 | ystd, 523 | wl_line, 524 | center_constraint, 525 | verbose, 526 | cont_width, 527 | w, 528 | SN_limit, 529 | rest_spectral_resolution, 530 | cosmo, 531 | ) -> Spectrum: 532 | """ 533 | Start with the model with the most components (constant + as many 534 | Gaussians as emission/absorption lines) and find the simplest model that well 535 | describes the data. In case the model does not work (i.e. 1 line or more is 536 | non-detected), the search removes that component and attempts to fit a model 537 | with fewer Gaussians until it finds a fit. For all non-detected lines, an 538 | upper limit is estimated. 539 | Input: 540 | redshift: redshift of the source 541 | x: usually an array of wavelengths 542 | y: usually an array of fluxes 543 | ystd: errors on y, usually a stdev of the flux 544 | wl_line: wavelengths of the lines to be fit; could be a single line or 545 | multiple lines; usually a singlet or a doublet 546 | center_constraint: fix, constrain or let free the centers of the Gaussians 547 | when fitting 548 | verbose: print full lmfit output 549 | sky: sky contaminated areas to mask. If None, nothing will be masked 550 | cont_width: the amount left and right of the lines used for continuum 551 | estimation 552 | mask_width: if another line B falls within the region selected for 553 | fitting line A, how big of a wavelength region should be 554 | covered for B 555 | w: region to be probed left and right of the starting wavelength solution 556 | when fitting the Gaussian 557 | SN_limit: signal to noise limit for detections 558 | rest_spectral_resolution: restframed FWHM of the instrument 559 | cosmo: cosmological parameters 560 | Return: 561 | Spectrum with continuum, detected lines and upper limits 562 | """ 563 | for wl_subset_indices in subsets(len(wl_line)): 564 | wl_subset = wl_line[list(wl_subset_indices)] 565 | model = fit_model( 566 | x, 567 | y, 568 | ystd, 569 | wl_subset, 570 | center_constraint, 571 | verbose, 572 | cont_width, 573 | w, 574 | rest_spectral_resolution, 575 | ) 576 | if is_good(model, SN_limit): 577 | break 578 | else: 579 | if verbose == True: 580 | print( 581 | Fore.BLUE 582 | + f"NonDetection when fitting set of lines {wl_subset}, try a simpler model" 583 | ) 584 | else: 585 | return None 586 | 587 | # Calculate upper limit, by subtracting best fit model and then calculating 588 | # the upper limit from the rms noise on the residuals 589 | residual = y.value - model.eval(x=x.value) 590 | ul = upper_limit(residual, x.value, SN_limit, rest_spectral_resolution.value) 591 | 592 | # Print a note when the spectrum does not cover one of the lines to be fit 593 | if verbose == True: 594 | for i, wl in enumerate(wl_line): 595 | if (i not in wl_subset_indices) and np.sum( 596 | ~so.mask_line(x, wl_line[i], 1.01 * rest_spectral_resolution,) 597 | ) < rest_spectral_resolution // so.dispersion(x): 598 | print(Fore.BLUE + f"No spectral coverage on line {wl_line[i]}") 599 | else: 600 | continue 601 | 602 | # Add line measurements, nondetections and lines without coverage into a 603 | # Spectrum class format 604 | fitparams = model.params 605 | return Spectrum( 606 | continuum=RandomVariable.from_param(fitparams["c"]) * y.unit, 607 | lines=[ 608 | Line( 609 | wavelength=RandomVariable.from_param( 610 | fitparams[f"g{wl_subset_indices.index(i)}_center"] 611 | ) 612 | * x.unit, 613 | height=RandomVariable.from_param( 614 | fitparams[f"g{wl_subset_indices.index(i)}_height"] 615 | ) 616 | * y.unit, 617 | sigma=RandomVariable.from_param( 618 | fitparams[f"g{wl_subset_indices.index(i)}_sigma"] 619 | ) 620 | * x.unit, 621 | amplitude=RandomVariable.from_param( 622 | fitparams[f"g{wl_subset_indices.index(i)}_amplitude"] 623 | ) 624 | * x.unit 625 | * y.unit, 626 | fwhm=RandomVariable.from_param( 627 | fitparams[f"g{wl_subset_indices.index(i)}_fwhm"] 628 | ) 629 | * x.unit, 630 | z=redshift, 631 | restwl=wl_line[i], 632 | continuum=RandomVariable.from_param(fitparams["c"]) * y.unit, 633 | cosmo=cosmo, 634 | resolution=rest_spectral_resolution, 635 | ) 636 | if i in wl_subset_indices 637 | else NoCoverage( 638 | z=redshift, 639 | restwl=wl_line[i], 640 | continuum=RandomVariable.from_param(fitparams["c"]) * y.unit, 641 | ) 642 | if np.sum(~so.mask_line(x, wl_line[i], 1.01 * rest_spectral_resolution,)) 643 | < rest_spectral_resolution // so.dispersion(x) 644 | else NonDetection( 645 | amplitude=ul * x.unit * y.unit, 646 | z=redshift, 647 | restwl=wl_line[i], 648 | continuum=RandomVariable.from_param(fitparams["c"]) * y.unit, 649 | cosmo=cosmo, 650 | ) 651 | for i in range(len(wl_line)) 652 | ], 653 | ) 654 | 655 | 656 | def fit_model( 657 | x, 658 | y, 659 | ystd, 660 | wl_line, 661 | center_constraint, 662 | verbose, 663 | cont_width, 664 | w, 665 | rest_spectral_resolution, 666 | ) -> ModelResult: 667 | """ 668 | Fits a number of Gaussians plus a constant continuum to the given data with 669 | the package `lmfit' 670 | !!! Assumes the spectra are restframe !!! 671 | Input: 672 | redshift: redshift of the source 673 | x: usually an array of wavelengths 674 | y: usually an array of fluxes 675 | ystd: errors on y, usually a stdev of the flux 676 | wl_line: wavelengths of the lines to be fit; could be a single line or 677 | multiple lines; usually a singlet or a doublet 678 | center_constraint: fix, constrain or let free the centers of the Gaussians 679 | when fitting 680 | verbose: print full lmfit output 681 | sky: sky contaminated areas to mask. If None, nothing will be masked 682 | cont_width: the amount left and right of the lines used for continuum 683 | estimation 684 | mask_width: if another line B falls within the region selected for 685 | fitting line A, how big of a wavelength region should be 686 | covered for B 687 | w: region to be probed left and right of the starting wavelength solution 688 | when fitting the Gaussian 689 | SN_limit: signal to noise limit for detections 690 | rest_spectral_resolution: restframed FWHM of the instrument 691 | cosmo: cosmological parameters 692 | Output: 693 | parameter fits of the Gaussian(s) + continuum 694 | """ 695 | x = x.astype(dtype=np.float64) 696 | y = y.astype(dtype=np.float64) 697 | ystd = ystd.astype(dtype=np.float64) 698 | # make a model that is a number of Gaussians + a constant: 699 | model = sum( 700 | (GaussianModel(prefix=f"g{i}_") for i in range(len(wl_line.to(x.unit).value))), 701 | ConstantModel(), 702 | ) 703 | 704 | # rescale the flux scale to get numbers comparable to the wavelength and 705 | # avoid numerical instabilities 706 | flux_scale = 1.0 / np.std(y).value * cont_width.to(x.unit).value 707 | y = y * flux_scale 708 | ystd = ystd * flux_scale 709 | 710 | # define the model parameters, i.e. the continuum, wavelength of line we are 711 | # fitting, rough estimates for the sigma and height of the peak 712 | 713 | # fix the center of the Gaussian for sources where the normal fit does not 714 | # want to work 715 | if center_constraint == "fixed": 716 | for i in range(len(wl_line)): 717 | model.set_param_hint(f"g{i}_center", vary=False) 718 | 719 | # Constrain the center within a small range around the expected wavelength 720 | # of the emission line 721 | elif center_constraint == "constrained": 722 | for i, wl in enumerate(wl_line): 723 | model.set_param_hint( 724 | f"g{i}_center", value=wl.to(x.unit).value, min=(wl - w).to(x.unit).value, max=(wl + w).to(x.unit).value, 725 | ) 726 | 727 | # If no fixing or constraining is done, then constrain the center to be 728 | # within the entire range selected around the line that is used for the 729 | # fitting 730 | else: 731 | for i, wl in enumerate(wl_line): 732 | model.set_param_hint( 733 | f"g{i}_center", 734 | value=wl.to(x.unit).value, 735 | min=(wl - cont_width).to(x.unit).value, 736 | max=(wl + cont_width).to(x.unit).value, 737 | ) 738 | 739 | for i, wl in enumerate(wl_line.to(x.unit).value): 740 | # FWHM & sigma: average between minimum and maximum expected width 741 | model.set_param_hint( 742 | f"g{i}_fwhm", 743 | value=rest_spectral_resolution.to(x.unit).value, 744 | min=rest_spectral_resolution.to(x.unit).value / 4, 745 | ) 746 | model.set_param_hint( 747 | f"g{i}_sigma", 748 | value=so.fwhm_to_sigma(rest_spectral_resolution.to(x.unit).value), 749 | min=so.fwhm_to_sigma(rest_spectral_resolution.to(x.unit).value / 4), 750 | ) 751 | # Height & amplitude: maximum y value - median of continuum 752 | model.set_param_hint( 753 | f"g{i}_height", value=max(y.value, key=abs) - np.median(y).value 754 | ) 755 | model.set_param_hint( 756 | f"g{i}_amplitude", 757 | value=so.height_to_amplitude( 758 | max(y.value, key=abs) - np.median(y).value, 759 | so.fwhm_to_sigma(rest_spectral_resolution.to(x.unit).value), 760 | ), 761 | ) 762 | 763 | # Set the continuum to the median of the selected range 764 | ctr = np.median(y).value 765 | params = model.make_params( 766 | c=ctr, **{f"g{i}_center": wl for i, wl in enumerate(wl_line.to(x.unit).value)} 767 | ) 768 | 769 | # perform a least squares fit with errors taken into account as i.e. 1/sigma 770 | try: 771 | result: ModelResult = model.fit( 772 | y.value, params, x=x.value, weights=1.0 / ystd.value 773 | ) 774 | if verbose == True: 775 | print(result.fit_report()) 776 | try: 777 | result.params["c"].value = result.params["c"].value / flux_scale 778 | except: 779 | pass 780 | try: 781 | result.params["c"].stderr = result.params["c"].stderr / flux_scale 782 | except: 783 | pass 784 | for i, wl in enumerate(wl_line.value): 785 | try: 786 | result.params[f"g{i}_amplitude"].value = ( 787 | result.params[f"g{i}_amplitude"].value / flux_scale 788 | ) 789 | except: 790 | pass 791 | try: 792 | result.params[f"g{i}_amplitude"].stderr = ( 793 | result.params[f"g{i}_amplitude"].stderr / flux_scale 794 | ) 795 | except: 796 | pass 797 | try: 798 | result.params[f"g{i}_height"].value = ( 799 | result.params[f"g{i}_height"].value / flux_scale 800 | ) 801 | except: 802 | pass 803 | try: 804 | result.params[f"g{i}_height"].stderr = ( 805 | result.params[f"g{i}_height"].stderr / flux_scale 806 | ) 807 | except: 808 | pass 809 | 810 | except: 811 | print(Fore.RED + "No model could be fit") 812 | sys.exit("Error!") 813 | return result 814 | 815 | 816 | def fit_lines( 817 | target, 818 | spectrum, 819 | line_list, 820 | line_groups, 821 | center_constraint, 822 | verbose, 823 | sky, 824 | cont_width, 825 | mask_width, 826 | w, 827 | SN_limit, 828 | rest_spectral_resolution, 829 | cosmo, 830 | ): 831 | """ 832 | Head function that goes through the list of lines and fits all lines for a 833 | particular spectrum 834 | Input: 835 | target: Astropy table with details about the target 836 | spectrum: Astropy table with spectrum 837 | list_list: Astropy list of all the lines that will be fit 838 | line_groups: Astropy list of lines that will be fit, connected into 839 | groups based on proximity; The grouped lines will be fit 840 | together as a sum of Gaussians 841 | center_constraint: fix, constrain or let free the centers of the Gaussians 842 | when fitting 843 | verbose: print full lmfit output 844 | sky: sky contaminated areas to mask. If None, nothing will be masked 845 | cont_width: the amount left and right of the lines used for continuum 846 | estimation 847 | mask_width: if another line B falls within the region selected for 848 | fitting line A, how big of a wavelength region should be 849 | covered for B 850 | w: region to be probed left and right of the starting wavelength solution 851 | when fitting the Gaussian 852 | SN_limit: signal to noise limit for detections 853 | rest_spectral_resolution: restframed FWHM of the instrument 854 | cosmo: cosmological parameters 855 | Output: 856 | Astropy table containing details of emission lines and the Gaussian fits 857 | to them 858 | """ 859 | for group in line_groups: 860 | select_group = (line_list["wavelength"] > group.beginning) & ( 861 | line_list["wavelength"] < group.ending 862 | ) 863 | if ( 864 | (line_list["wavelength"][select_group] < np.amin(spectrum["wl_rest"])).all() 865 | == True 866 | ) | ( 867 | (line_list["wavelength"][select_group] > np.amax(spectrum["wl_rest"])).all() 868 | == True 869 | ): 870 | continue 871 | else: 872 | spectrum_fit, spectrum_line = do_gaussian( 873 | line_list[select_group], 874 | line_list[~select_group], 875 | spectrum, 876 | target, 877 | center_constraint, 878 | verbose, 879 | sky, 880 | cont_width, 881 | mask_width, 882 | w, 883 | SN_limit, 884 | rest_spectral_resolution, 885 | cosmo, 886 | ) 887 | yield spectrum_fit, spectrum_line, line_list[select_group] 888 | 889 | 890 | def do_gaussian( 891 | selected_lines, 892 | other_lines, 893 | spectrum, 894 | target, 895 | center_constraint, 896 | verbose, 897 | sky, 898 | cont_width, 899 | mask_width, 900 | w, 901 | SN_limit, 902 | rest_spectral_resolution, 903 | cosmo, 904 | ): 905 | """ 906 | Selects the spectrum around an emission line of interest. Then fits a single 907 | Gaussian plus a constant continuum to the given data with the package `lmfit' 908 | !!! Assumes the spectrum is restframe !!! 909 | Input: 910 | selected_lines: lines to be fit jointly 911 | other_lines: other lines in the catalog. Is used to mask other lines 912 | that might contaminate the continuum estimation. 913 | spectrum: fits table containing, the wavelength, flux and error of flux 914 | target: fits row with properties of the source, esp. its redshift 915 | center_constraint: fix, constrain or let free the centers of the Gaussians 916 | when fitting 917 | verbose: print full lmfit output 918 | sky: sky contaminated areas to mask. If None, nothing will be masked 919 | cont_width: the amount left and right of the lines used for continuum 920 | estimation 921 | mask_width: if another line B falls within the region selected for 922 | fitting line A, how big of a wavelength region should be 923 | covered for B 924 | w: region to be probed left and right of the starting wavelength solution 925 | when fitting the Gaussian 926 | SN_limit: signal to noise limit for detections 927 | rest_spectral_resolution: restframed FWHM of the instrument 928 | cosmo: cosmological parameters 929 | Output: 930 | spectrum_fit: parameters of the fit around the doublet 931 | spectrum_line: extracted spectrum around the lines 932 | """ 933 | # Mask the region around the line 934 | mask_line = so.select_lines( 935 | selected_lines, other_lines, spectrum, target, sky, cont_width, mask_width, 936 | ) 937 | 938 | # Create a new variable that contains the spectrum around the line 939 | spectrum_line = spectrum[mask_line] 940 | 941 | # Fit the gaussian(s) 942 | spectrum_fit = model_selection( 943 | target["Redshift"], 944 | spectrum_line["wl_rest"], 945 | spectrum_line["flux_rest"], 946 | spectrum_line["stdev_rest"], 947 | selected_lines["wavelength"], 948 | center_constraint, 949 | verbose, 950 | cont_width, 951 | w, 952 | SN_limit, 953 | rest_spectral_resolution, 954 | cosmo, 955 | ) 956 | 957 | return spectrum_fit, spectrum_line 958 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "appnope" 5 | version = "0.1.3" 6 | description = "Disable App Nap on macOS >= 10.9" 7 | optional = false 8 | python-versions = "*" 9 | files = [ 10 | {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, 11 | {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, 12 | ] 13 | 14 | [[package]] 15 | name = "asteval" 16 | version = "0.9.29" 17 | description = "Safe, minimalistic evaluator of python expression using ast module" 18 | optional = false 19 | python-versions = ">=3.7" 20 | files = [ 21 | {file = "asteval-0.9.29-py3-none-any.whl", hash = "sha256:134e42fc4790582f2f926999e59abb444fb491046ba396836962268aad8a68a5"}, 22 | {file = "asteval-0.9.29.tar.gz", hash = "sha256:ab98c61ba9394149c774ae7861497e9c32580301aa693ca19746997216c31fab"}, 23 | ] 24 | 25 | [package.extras] 26 | all = ["Sphinx", "build", "coverage", "pytest", "pytest-cov", "twine"] 27 | dev = ["build", "twine"] 28 | doc = ["Sphinx"] 29 | test = ["coverage", "pytest", "pytest-cov"] 30 | 31 | [[package]] 32 | name = "astropy" 33 | version = "5.2.2" 34 | description = "Astronomy and astrophysics core library" 35 | optional = false 36 | python-versions = ">=3.8" 37 | files = [ 38 | {file = "astropy-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66522e897daf3766775c00ef5c63b69beb0eb359e1f45d18745d0f0ca7f29cc1"}, 39 | {file = "astropy-5.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ccf6f16cf7e520247ecc9d1a66dd4c3927fd60622203bdd1d06655ad81fa18f"}, 40 | {file = "astropy-5.2.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3d0c37da922cdcb81e74437118fabd64171cbfefa06c7ea697a270e82a8164f2"}, 41 | {file = "astropy-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04464e664a22382626ce9750ebe943b80a718dc8347134b9d138b63a2029f67a"}, 42 | {file = "astropy-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f60cea0fa7cb6ebbd90373e48c07f5d459e95dfd6363f50e316e2db7755bead"}, 43 | {file = "astropy-5.2.2-cp310-cp310-win32.whl", hash = "sha256:6c3abb2fa8ebaaad77875a02e664c1011f35bd0c0ef7d35a39b03c859de1129a"}, 44 | {file = "astropy-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:185ade8c33cea34ba791b282e937686d98b4e205d4f343e686a4666efab2f6e7"}, 45 | {file = "astropy-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f61c612e90e3dd3c075e99a61dedd53331c4577016c1d571aab00b95ca1731ab"}, 46 | {file = "astropy-5.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3881e933ea870a27e5d6896443401fbf51e3b7e57c6356f333553f5ff0070c72"}, 47 | {file = "astropy-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f210b5b4062030388437b9aca4bbf68f9063b2b27184006814a09fab41ac270e"}, 48 | {file = "astropy-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e14b5a22f24ae5cf0404f21a4de135e26ca3c9cf55aefc5b0264a9ce24b53b0b"}, 49 | {file = "astropy-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6768b3a670cdfff6c2416b3d7d1e4231839608299b32367e8b095959fc6733a6"}, 50 | {file = "astropy-5.2.2-cp311-cp311-win32.whl", hash = "sha256:0aad85604cad40189b13d66bb46fb2a95df1a9095992071b31c3fa35b476fdbc"}, 51 | {file = "astropy-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:ac944158794a88789a007892ad91db35da14f689da1ab37c33c8de770a27f717"}, 52 | {file = "astropy-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6703860deecd384bba2d2e338f77a0e7b46672812d27ed15f95e8faaa89fcd35"}, 53 | {file = "astropy-5.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:124ef2a9f9b1cdbc1a5d514f7e57538253bb67ad031215f5f5405fc4cd31a4cd"}, 54 | {file = "astropy-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:800501cc626aef0780dfb66156619699e98cb48854ed710f1ae3708aaab79f6e"}, 55 | {file = "astropy-5.2.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22396592aa9b1653d37d552d3c52a8bb27ef072d077fad43b64faf841b1dcbf3"}, 56 | {file = "astropy-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093782b1f0177c3dd2c04181ec016d8e569bd9e862b48236e40b14e2a7399170"}, 57 | {file = "astropy-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0c664f9194a4a3cece6215f651a9bc22c3cbd1f52dd450bd4d94eaf36f13c06c"}, 58 | {file = "astropy-5.2.2-cp38-cp38-win32.whl", hash = "sha256:35ce00bb3dbc8bf7c842a0635354a5023cb64ae9c1925aa9b54629cf7fed2abe"}, 59 | {file = "astropy-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:8304b590b20f9c161db85d5eb65d4c6323b3370a17c96ae163b18a0071cbd68a"}, 60 | {file = "astropy-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:855748c2f1aedee5d770dfec8334109f1bcd1c1cee97f5915d3e888f43c04acf"}, 61 | {file = "astropy-5.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ef9acc55c5fd70c7c78370389e79fb044321e531ac1facb7bddeef89d3132e3"}, 62 | {file = "astropy-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f30b5d153b9d119783b96b948a3e0c4eb668820c06d2e8ba72f6ea989e4af5c1"}, 63 | {file = "astropy-5.2.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:530e6911a54a42e9f15b1a75dc3c699be3946c0b6ffdcfdcf4e14ae5fcfcd236"}, 64 | {file = "astropy-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae3b383ac84fe6765e275f897f4010cc6afe6933607b7468561414dffdc4d915"}, 65 | {file = "astropy-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b00a4cd49f8264a338b0020717bff104fbcca800bd50bf0a415d952078258a39"}, 66 | {file = "astropy-5.2.2-cp39-cp39-win32.whl", hash = "sha256:b7167b9965ebd78b7c9da7e98a943381b25e23d041bd304ec2e35e8ec811cefc"}, 67 | {file = "astropy-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:df81b8f23c5e906d799b47d2d8462707c745df38cafae0cd6674ef09e9a41789"}, 68 | {file = "astropy-5.2.2.tar.gz", hash = "sha256:e6a9e34716bda5945788353c63f0644721ee7e5447d16b1cdcb58c48a96b0d9c"}, 69 | ] 70 | 71 | [package.dependencies] 72 | numpy = ">=1.20" 73 | packaging = ">=19.0" 74 | pyerfa = ">=2.0" 75 | PyYAML = ">=3.13" 76 | 77 | [package.extras] 78 | all = ["asdf (>=2.10.0)", "beautifulsoup4", "bleach", "bottleneck", "certifi", "dask[array]", "fsspec[http] (>=2022.8.2)", "h5py", "html5lib", "ipython (>=4.2)", "jplephem", "matplotlib (>=3.1,!=3.4.0,!=3.5.2)", "mpmath", "pandas", "pyarrow (>=5.0.0)", "pytest (>=7.0)", "pytz", "s3fs (>=2022.8.2)", "scipy (>=1.5)", "sortedcontainers", "typing-extensions (>=3.10.0.1)"] 79 | docs = ["Jinja2 (>=3.0)", "matplotlib (>=3.1,!=3.4.0,!=3.5.2)", "pytest (>=7.0)", "scipy (>=1.3)", "sphinx", "sphinx-astropy (>=1.6)", "sphinx-changelog (>=1.2.0)"] 80 | recommended = ["matplotlib (>=3.1,!=3.4.0,!=3.5.2)", "scipy (>=1.5)"] 81 | test = ["pytest (>=7.0)", "pytest-astropy (>=0.10)", "pytest-astropy-header (>=0.2.1)", "pytest-doctestplus (>=0.12)", "pytest-xdist"] 82 | test-all = ["coverage[toml]", "ipython (>=4.2)", "objgraph", "pytest (>=7.0)", "pytest-astropy (>=0.10)", "pytest-astropy-header (>=0.2.1)", "pytest-doctestplus (>=0.12)", "pytest-xdist", "sgp4 (>=2.3)", "skyfield (>=1.20)"] 83 | 84 | [[package]] 85 | name = "backcall" 86 | version = "0.2.0" 87 | description = "Specifications for callback functions passed in to an API" 88 | optional = false 89 | python-versions = "*" 90 | files = [ 91 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 92 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 93 | ] 94 | 95 | [[package]] 96 | name = "black" 97 | version = "23.3.0" 98 | description = "The uncompromising code formatter." 99 | optional = false 100 | python-versions = ">=3.7" 101 | files = [ 102 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, 103 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, 104 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, 105 | {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, 106 | {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, 107 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, 108 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, 109 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, 110 | {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, 111 | {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, 112 | {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, 113 | {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, 114 | {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, 115 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, 116 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, 117 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, 118 | {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, 119 | {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, 120 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, 121 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, 122 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, 123 | {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, 124 | {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, 125 | {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, 126 | {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, 127 | ] 128 | 129 | [package.dependencies] 130 | click = ">=8.0.0" 131 | mypy-extensions = ">=0.4.3" 132 | packaging = ">=22.0" 133 | pathspec = ">=0.9.0" 134 | platformdirs = ">=2" 135 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 136 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 137 | 138 | [package.extras] 139 | colorama = ["colorama (>=0.4.3)"] 140 | d = ["aiohttp (>=3.7.4)"] 141 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 142 | uvloop = ["uvloop (>=0.15.2)"] 143 | 144 | [[package]] 145 | name = "click" 146 | version = "8.1.3" 147 | description = "Composable command line interface toolkit" 148 | optional = false 149 | python-versions = ">=3.7" 150 | files = [ 151 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 152 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 153 | ] 154 | 155 | [package.dependencies] 156 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 157 | 158 | [[package]] 159 | name = "colorama" 160 | version = "0.4.6" 161 | description = "Cross-platform colored terminal text." 162 | optional = false 163 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 164 | files = [ 165 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 166 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 167 | ] 168 | 169 | [[package]] 170 | name = "contourpy" 171 | version = "1.0.7" 172 | description = "Python library for calculating contours of 2D quadrilateral grids" 173 | optional = false 174 | python-versions = ">=3.8" 175 | files = [ 176 | {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"}, 177 | {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"}, 178 | {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"}, 179 | {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"}, 180 | {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"}, 181 | {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"}, 182 | {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"}, 183 | {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"}, 184 | {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"}, 185 | {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"}, 186 | {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"}, 187 | {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"}, 188 | {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"}, 189 | {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"}, 190 | {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"}, 191 | {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"}, 192 | {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"}, 193 | {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"}, 194 | {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"}, 195 | {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"}, 196 | {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"}, 197 | {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"}, 198 | {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"}, 199 | {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"}, 200 | {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"}, 201 | {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"}, 202 | {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"}, 203 | {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"}, 204 | {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"}, 205 | {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"}, 206 | {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"}, 207 | {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"}, 208 | {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"}, 209 | {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"}, 210 | {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"}, 211 | {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"}, 212 | {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"}, 213 | {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"}, 214 | {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"}, 215 | {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"}, 216 | {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"}, 217 | {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"}, 218 | {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"}, 219 | {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"}, 220 | {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"}, 221 | {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"}, 222 | {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"}, 223 | {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"}, 224 | {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"}, 225 | {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"}, 226 | {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"}, 227 | {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"}, 228 | {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"}, 229 | {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"}, 230 | {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"}, 231 | ] 232 | 233 | [package.dependencies] 234 | numpy = ">=1.16" 235 | 236 | [package.extras] 237 | bokeh = ["bokeh", "chromedriver", "selenium"] 238 | docs = ["furo", "sphinx-copybutton"] 239 | mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"] 240 | test = ["Pillow", "matplotlib", "pytest"] 241 | test-no-images = ["pytest"] 242 | 243 | [[package]] 244 | name = "cycler" 245 | version = "0.11.0" 246 | description = "Composable style cycles" 247 | optional = false 248 | python-versions = ">=3.6" 249 | files = [ 250 | {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, 251 | {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, 252 | ] 253 | 254 | [[package]] 255 | name = "decorator" 256 | version = "5.1.1" 257 | description = "Decorators for Humans" 258 | optional = false 259 | python-versions = ">=3.5" 260 | files = [ 261 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, 262 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, 263 | ] 264 | 265 | [[package]] 266 | name = "fonttools" 267 | version = "4.39.4" 268 | description = "Tools to manipulate font files" 269 | optional = false 270 | python-versions = ">=3.8" 271 | files = [ 272 | {file = "fonttools-4.39.4-py3-none-any.whl", hash = "sha256:106caf6167c4597556b31a8d9175a3fdc0356fdcd70ab19973c3b0d4c893c461"}, 273 | {file = "fonttools-4.39.4.zip", hash = "sha256:dba8d7cdb8e2bac1b3da28c5ed5960de09e59a2fe7e63bb73f5a59e57b0430d2"}, 274 | ] 275 | 276 | [package.extras] 277 | all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] 278 | graphite = ["lz4 (>=1.7.4.2)"] 279 | interpolatable = ["munkres", "scipy"] 280 | lxml = ["lxml (>=4.0,<5)"] 281 | pathops = ["skia-pathops (>=0.5.0)"] 282 | plot = ["matplotlib"] 283 | repacker = ["uharfbuzz (>=0.23.0)"] 284 | symfont = ["sympy"] 285 | type1 = ["xattr"] 286 | ufo = ["fs (>=2.2.0,<3)"] 287 | unicode = ["unicodedata2 (>=15.0.0)"] 288 | woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] 289 | 290 | [[package]] 291 | name = "future" 292 | version = "0.18.3" 293 | description = "Clean single-source support for Python 3 and 2" 294 | optional = false 295 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 296 | files = [ 297 | {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, 298 | ] 299 | 300 | [[package]] 301 | name = "importlib-resources" 302 | version = "5.12.0" 303 | description = "Read resources from Python packages" 304 | optional = false 305 | python-versions = ">=3.7" 306 | files = [ 307 | {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, 308 | {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, 309 | ] 310 | 311 | [package.dependencies] 312 | zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} 313 | 314 | [package.extras] 315 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 316 | testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 317 | 318 | [[package]] 319 | name = "ipython" 320 | version = "7.34.0" 321 | description = "IPython: Productive Interactive Computing" 322 | optional = false 323 | python-versions = ">=3.7" 324 | files = [ 325 | {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, 326 | {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, 327 | ] 328 | 329 | [package.dependencies] 330 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 331 | backcall = "*" 332 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 333 | decorator = "*" 334 | jedi = ">=0.16" 335 | matplotlib-inline = "*" 336 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} 337 | pickleshare = "*" 338 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 339 | pygments = "*" 340 | setuptools = ">=18.5" 341 | traitlets = ">=4.2" 342 | 343 | [package.extras] 344 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] 345 | doc = ["Sphinx (>=1.3)"] 346 | kernel = ["ipykernel"] 347 | nbconvert = ["nbconvert"] 348 | nbformat = ["nbformat"] 349 | notebook = ["ipywidgets", "notebook"] 350 | parallel = ["ipyparallel"] 351 | qtconsole = ["qtconsole"] 352 | test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] 353 | 354 | [[package]] 355 | name = "jedi" 356 | version = "0.18.2" 357 | description = "An autocompletion tool for Python that can be used for text editors." 358 | optional = false 359 | python-versions = ">=3.6" 360 | files = [ 361 | {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, 362 | {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, 363 | ] 364 | 365 | [package.dependencies] 366 | parso = ">=0.8.0,<0.9.0" 367 | 368 | [package.extras] 369 | docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] 370 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 371 | testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] 372 | 373 | [[package]] 374 | name = "kiwisolver" 375 | version = "1.4.4" 376 | description = "A fast implementation of the Cassowary constraint solver" 377 | optional = false 378 | python-versions = ">=3.7" 379 | files = [ 380 | {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, 381 | {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, 382 | {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, 383 | {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, 384 | {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, 385 | {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, 386 | {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, 387 | {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, 388 | {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, 389 | {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, 390 | {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, 391 | {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, 392 | {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, 393 | {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, 394 | {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, 395 | {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, 396 | {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, 397 | {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, 398 | {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, 399 | {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, 400 | {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, 401 | {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, 402 | {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, 403 | {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, 404 | {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, 405 | {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, 406 | {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, 407 | {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, 408 | {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, 409 | {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, 410 | {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, 411 | {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, 412 | {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, 413 | {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, 414 | {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, 415 | {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, 416 | {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, 417 | {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, 418 | {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, 419 | {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, 420 | {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, 421 | {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, 422 | {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, 423 | {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, 424 | {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, 425 | {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, 426 | {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, 427 | {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, 428 | {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, 429 | {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, 430 | {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, 431 | {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, 432 | {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, 433 | {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, 434 | {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, 435 | {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, 436 | {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, 437 | {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, 438 | {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, 439 | {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, 440 | {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, 441 | {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, 442 | {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, 443 | {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, 444 | {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, 445 | {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, 446 | {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, 447 | {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, 448 | ] 449 | 450 | [[package]] 451 | name = "lmfit" 452 | version = "1.2.1" 453 | description = "Least-Squares Minimization with Bounds and Constraints" 454 | optional = false 455 | python-versions = ">=3.7" 456 | files = [ 457 | {file = "lmfit-1.2.1-py3-none-any.whl", hash = "sha256:26d7ea65c056500dbe2da6fd58b2301a722c59c4549b22ab533165cd8e364233"}, 458 | {file = "lmfit-1.2.1.tar.gz", hash = "sha256:00cef5bd145bf81b73630b78926af24b24f1805421e6211ca585588aa7cc415b"}, 459 | ] 460 | 461 | [package.dependencies] 462 | asteval = ">=0.9.28" 463 | numpy = ">=1.19" 464 | scipy = ">=1.6" 465 | uncertainties = ">=3.1.4" 466 | 467 | [package.extras] 468 | all = ["Pillow", "Sphinx", "build", "cairosvg", "check-wheel-contents", "corner", "coverage", "dill", "emcee (>=3.0.0)", "flaky", "ipykernel", "jupyter-sphinx (>=0.2.4)", "matplotlib", "numdifftools", "pandas", "pre-commit", "pycairo", "pytest", "pytest-cov", "sphinx-gallery (>=0.10)", "sphinxcontrib-svg2pdfconverter", "sympy", "twine"] 469 | dev = ["build", "check-wheel-contents", "pre-commit", "twine"] 470 | doc = ["Pillow", "Sphinx", "cairosvg", "corner", "dill", "emcee (>=3.0.0)", "ipykernel", "jupyter-sphinx (>=0.2.4)", "matplotlib", "numdifftools", "pandas", "pycairo", "sphinx-gallery (>=0.10)", "sphinxcontrib-svg2pdfconverter", "sympy"] 471 | test = ["coverage", "flaky", "pytest", "pytest-cov"] 472 | 473 | [[package]] 474 | name = "matplotlib" 475 | version = "3.7.1" 476 | description = "Python plotting package" 477 | optional = false 478 | python-versions = ">=3.8" 479 | files = [ 480 | {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, 481 | {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, 482 | {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"}, 483 | {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"}, 484 | {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"}, 485 | {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"}, 486 | {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"}, 487 | {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"}, 488 | {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"}, 489 | {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"}, 490 | {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"}, 491 | {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"}, 492 | {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"}, 493 | {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"}, 494 | {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"}, 495 | {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"}, 496 | {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"}, 497 | {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"}, 498 | {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"}, 499 | {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"}, 500 | {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"}, 501 | {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"}, 502 | {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"}, 503 | {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"}, 504 | {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"}, 505 | {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"}, 506 | {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"}, 507 | {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"}, 508 | {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"}, 509 | {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"}, 510 | {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"}, 511 | {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"}, 512 | {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"}, 513 | {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"}, 514 | {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"}, 515 | {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"}, 516 | {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"}, 517 | {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"}, 518 | {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"}, 519 | {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, 520 | {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, 521 | ] 522 | 523 | [package.dependencies] 524 | contourpy = ">=1.0.1" 525 | cycler = ">=0.10" 526 | fonttools = ">=4.22.0" 527 | importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} 528 | kiwisolver = ">=1.0.1" 529 | numpy = ">=1.20" 530 | packaging = ">=20.0" 531 | pillow = ">=6.2.0" 532 | pyparsing = ">=2.3.1" 533 | python-dateutil = ">=2.7" 534 | 535 | [[package]] 536 | name = "matplotlib-inline" 537 | version = "0.1.6" 538 | description = "Inline Matplotlib backend for Jupyter" 539 | optional = false 540 | python-versions = ">=3.5" 541 | files = [ 542 | {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, 543 | {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, 544 | ] 545 | 546 | [package.dependencies] 547 | traitlets = "*" 548 | 549 | [[package]] 550 | name = "mypy" 551 | version = "1.3.0" 552 | description = "Optional static typing for Python" 553 | optional = false 554 | python-versions = ">=3.7" 555 | files = [ 556 | {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, 557 | {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, 558 | {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, 559 | {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, 560 | {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, 561 | {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, 562 | {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, 563 | {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, 564 | {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, 565 | {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, 566 | {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, 567 | {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, 568 | {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, 569 | {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, 570 | {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, 571 | {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, 572 | {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, 573 | {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, 574 | {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, 575 | {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, 576 | {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, 577 | {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, 578 | {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, 579 | {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, 580 | {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, 581 | {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, 582 | ] 583 | 584 | [package.dependencies] 585 | mypy-extensions = ">=1.0.0" 586 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 587 | typing-extensions = ">=3.10" 588 | 589 | [package.extras] 590 | dmypy = ["psutil (>=4.0)"] 591 | install-types = ["pip"] 592 | python2 = ["typed-ast (>=1.4.0,<2)"] 593 | reports = ["lxml"] 594 | 595 | [[package]] 596 | name = "mypy-extensions" 597 | version = "1.0.0" 598 | description = "Type system extensions for programs checked with the mypy type checker." 599 | optional = false 600 | python-versions = ">=3.5" 601 | files = [ 602 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 603 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 604 | ] 605 | 606 | [[package]] 607 | name = "numpy" 608 | version = "1.24.3" 609 | description = "Fundamental package for array computing in Python" 610 | optional = false 611 | python-versions = ">=3.8" 612 | files = [ 613 | {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, 614 | {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, 615 | {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, 616 | {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, 617 | {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, 618 | {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, 619 | {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, 620 | {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, 621 | {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, 622 | {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, 623 | {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, 624 | {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, 625 | {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, 626 | {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, 627 | {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, 628 | {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, 629 | {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, 630 | {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, 631 | {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, 632 | {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, 633 | {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, 634 | {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, 635 | {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, 636 | {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, 637 | {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, 638 | {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, 639 | {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, 640 | {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, 641 | ] 642 | 643 | [[package]] 644 | name = "packaging" 645 | version = "23.1" 646 | description = "Core utilities for Python packages" 647 | optional = false 648 | python-versions = ">=3.7" 649 | files = [ 650 | {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, 651 | {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, 652 | ] 653 | 654 | [[package]] 655 | name = "parso" 656 | version = "0.8.3" 657 | description = "A Python Parser" 658 | optional = false 659 | python-versions = ">=3.6" 660 | files = [ 661 | {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, 662 | {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, 663 | ] 664 | 665 | [package.extras] 666 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 667 | testing = ["docopt", "pytest (<6.0.0)"] 668 | 669 | [[package]] 670 | name = "pathspec" 671 | version = "0.11.1" 672 | description = "Utility library for gitignore style pattern matching of file paths." 673 | optional = false 674 | python-versions = ">=3.7" 675 | files = [ 676 | {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, 677 | {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, 678 | ] 679 | 680 | [[package]] 681 | name = "pexpect" 682 | version = "4.8.0" 683 | description = "Pexpect allows easy control of interactive console applications." 684 | optional = false 685 | python-versions = "*" 686 | files = [ 687 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 688 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 689 | ] 690 | 691 | [package.dependencies] 692 | ptyprocess = ">=0.5" 693 | 694 | [[package]] 695 | name = "pickleshare" 696 | version = "0.7.5" 697 | description = "Tiny 'shelve'-like database with concurrency support" 698 | optional = false 699 | python-versions = "*" 700 | files = [ 701 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 702 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 703 | ] 704 | 705 | [[package]] 706 | name = "pillow" 707 | version = "9.5.0" 708 | description = "Python Imaging Library (Fork)" 709 | optional = false 710 | python-versions = ">=3.7" 711 | files = [ 712 | {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, 713 | {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, 714 | {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, 715 | {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, 716 | {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, 717 | {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, 718 | {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, 719 | {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, 720 | {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, 721 | {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, 722 | {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, 723 | {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, 724 | {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, 725 | {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, 726 | {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, 727 | {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, 728 | {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, 729 | {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, 730 | {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, 731 | {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, 732 | {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, 733 | {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, 734 | {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, 735 | {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, 736 | {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, 737 | {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, 738 | {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, 739 | {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, 740 | {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, 741 | {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, 742 | {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, 743 | {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, 744 | {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, 745 | {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, 746 | {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, 747 | {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, 748 | {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, 749 | {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, 750 | {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, 751 | {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, 752 | {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, 753 | {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, 754 | {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, 755 | {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, 756 | {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, 757 | {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, 758 | {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, 759 | {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, 760 | {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, 761 | {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, 762 | {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, 763 | {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, 764 | {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, 765 | {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, 766 | {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, 767 | {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, 768 | {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, 769 | {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, 770 | {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, 771 | {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, 772 | {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, 773 | {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, 774 | {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, 775 | {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, 776 | {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, 777 | {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, 778 | ] 779 | 780 | [package.extras] 781 | docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] 782 | tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] 783 | 784 | [[package]] 785 | name = "platformdirs" 786 | version = "3.5.1" 787 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 788 | optional = false 789 | python-versions = ">=3.7" 790 | files = [ 791 | {file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"}, 792 | {file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"}, 793 | ] 794 | 795 | [package.extras] 796 | docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] 797 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 798 | 799 | [[package]] 800 | name = "prompt-toolkit" 801 | version = "3.0.38" 802 | description = "Library for building powerful interactive command lines in Python" 803 | optional = false 804 | python-versions = ">=3.7.0" 805 | files = [ 806 | {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"}, 807 | {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"}, 808 | ] 809 | 810 | [package.dependencies] 811 | wcwidth = "*" 812 | 813 | [[package]] 814 | name = "ptyprocess" 815 | version = "0.7.0" 816 | description = "Run a subprocess in a pseudo terminal" 817 | optional = false 818 | python-versions = "*" 819 | files = [ 820 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 821 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 822 | ] 823 | 824 | [[package]] 825 | name = "pydantic" 826 | version = "1.10.7" 827 | description = "Data validation and settings management using python type hints" 828 | optional = false 829 | python-versions = ">=3.7" 830 | files = [ 831 | {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, 832 | {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, 833 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, 834 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, 835 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, 836 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, 837 | {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, 838 | {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, 839 | {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, 840 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, 841 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, 842 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, 843 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, 844 | {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, 845 | {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, 846 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, 847 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, 848 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, 849 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, 850 | {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, 851 | {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, 852 | {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, 853 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, 854 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, 855 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, 856 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, 857 | {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, 858 | {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, 859 | {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, 860 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, 861 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, 862 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, 863 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, 864 | {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, 865 | {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, 866 | {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, 867 | ] 868 | 869 | [package.dependencies] 870 | typing-extensions = ">=4.2.0" 871 | 872 | [package.extras] 873 | dotenv = ["python-dotenv (>=0.10.4)"] 874 | email = ["email-validator (>=1.0.3)"] 875 | 876 | [[package]] 877 | name = "pyerfa" 878 | version = "2.0.0.3" 879 | description = "Python bindings for ERFA" 880 | optional = false 881 | python-versions = ">=3.7" 882 | files = [ 883 | {file = "pyerfa-2.0.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:676515861ca3f0cb9d7e693389233e7126413a5ba93a0cc4d36b8ca933951e8d"}, 884 | {file = "pyerfa-2.0.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a438865894d226247dcfcb60d683ae075a52716504537052371b2b73458fe4fc"}, 885 | {file = "pyerfa-2.0.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73bf7d23f069d47632a2feeb1e73454b10392c4f3c16116017a6983f1f0e9b2b"}, 886 | {file = "pyerfa-2.0.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:780b0f90adf500b8ba24e9d509a690576a7e8287e354cfb90227c5963690d3fc"}, 887 | {file = "pyerfa-2.0.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5447bb45ddedde3052693c86b941a4908f5dbeb4a697bda45b5b89de92cfb74a"}, 888 | {file = "pyerfa-2.0.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c24e7960c6cdd3fa3f4dba5f3444a106ad48c94ff0b19eebaee06a142c18c52"}, 889 | {file = "pyerfa-2.0.0.3-cp310-cp310-win32.whl", hash = "sha256:170a83bd0243da518119b846f296cf33fa03f1f884a88578c1a38560182cf64e"}, 890 | {file = "pyerfa-2.0.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:51aa6e0faa4aa9ad8f0eef1c47fec76c5bebc0da7023a436089bdd6e5cfd625f"}, 891 | {file = "pyerfa-2.0.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fa9fceeb78057bfff7ae3aa6cdad3f1b193722de22bdbb75319256f4a9e2f76"}, 892 | {file = "pyerfa-2.0.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a8a2029fc62ff2369d01219f66a5ce6aed35ef33eddb06118b6c27e8573a9ed8"}, 893 | {file = "pyerfa-2.0.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da888da2c8db5a78273fbf0af4e74f04e2d312d371c3c021cf6c3b14fa60fe3b"}, 894 | {file = "pyerfa-2.0.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7354753addba5261ec1cbf1ba45784ed3a5c42da565ecc6e0aa36b7a17fa4689"}, 895 | {file = "pyerfa-2.0.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b55f7278c1dd362648d7956e1a5365ade5fed2fe5541b721b3ceb5271128892"}, 896 | {file = "pyerfa-2.0.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:23e5efcf96ed7161d74f79ca261d255e1f36988843d22cd97d8f60fe9c868d44"}, 897 | {file = "pyerfa-2.0.0.3-cp311-cp311-win32.whl", hash = "sha256:f0e9d0b122c454bcad5dbd0c3283b200783031d3f99ca9c550f49a7a7d4c41ea"}, 898 | {file = "pyerfa-2.0.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:09af83540e23a7d61a8368b0514b3daa4ed967e1e52d0add4f501f58c500dd7f"}, 899 | {file = "pyerfa-2.0.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a07444fd53a5dd18d7955f86f8d9b1be9a68ceb143e1145c0019a310c913c04"}, 900 | {file = "pyerfa-2.0.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf7364e475cff1f973e2fcf6962de9df9642c8802b010e29b2c592ae337e3c5"}, 901 | {file = "pyerfa-2.0.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8458421166f6ffe2e259aaf4aaa6e802d6539649a40e3194a81d30dccdc167a"}, 902 | {file = "pyerfa-2.0.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96ea688341176ae6220cc4743cda655549d71e3e3b60c5a99d02d5912d0ddf55"}, 903 | {file = "pyerfa-2.0.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d56f6b5a0a3ed7b80d630041829463a872946df277259b5453298842d42a54a4"}, 904 | {file = "pyerfa-2.0.0.3-cp37-cp37m-win32.whl", hash = "sha256:3ecb598924ddb4ea2b06efc6f1e55ca70897ed178a690e2eaa1e290448466c7c"}, 905 | {file = "pyerfa-2.0.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1033fdb890ec70d3a511e20a464afc8abbea2180108f27b14d8f1d1addc38cbe"}, 906 | {file = "pyerfa-2.0.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d8c0dbb17119e52def33f9d6dbf2deaf2113ed3e657b6ff692df9b6a3598397"}, 907 | {file = "pyerfa-2.0.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8a1edd2cbe4ead3bf9a51e578d5d83bdd7ab3b3ccb69e09b89a4c42aa5b35ffb"}, 908 | {file = "pyerfa-2.0.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04c3b715c924b6f972dd440a94a701a16a07700bc8ba9e88b1df765bdc36ad0"}, 909 | {file = "pyerfa-2.0.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d01c341c45b860ee5c7585ef003118c8015e9d65c30668d2f5bf657e1dcdd68"}, 910 | {file = "pyerfa-2.0.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24d89ead30edc6038408336ad9b696683e74c4eef550708fca6afef3ecd5b010"}, 911 | {file = "pyerfa-2.0.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b8c5e74d48a505a014e855cd4c7be11604901d94fd6f34b685f6720b7b20ed8"}, 912 | {file = "pyerfa-2.0.0.3-cp38-cp38-win32.whl", hash = "sha256:2ccba04de166d81bdd3adcf10428d908ce2f3a56ed1c2767d740fec12680edbd"}, 913 | {file = "pyerfa-2.0.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3df87743e27588c5bd5e1f3a886629b3277fdd418059ca048420d33169376775"}, 914 | {file = "pyerfa-2.0.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88aa1acedf298d255cc4b0740ee11a3b303b71763dba2f039d48abf0a95cf9df"}, 915 | {file = "pyerfa-2.0.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06d4f08e96867b1fc3ae9a9e4b38693ed0806463288efc41473ad16e14774504"}, 916 | {file = "pyerfa-2.0.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1819e0d95ff8dead80614f8063919d82b2dbb55437b6c0109d3393c1ab55954"}, 917 | {file = "pyerfa-2.0.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61f1097ac2ee8c15a2a636cdfb99340d708574d66f4610456bd457d1e6b852f4"}, 918 | {file = "pyerfa-2.0.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f42ee01a62c6cbba58103e6f8e600b21ad3a71262dccf03d476efb4a20ea71"}, 919 | {file = "pyerfa-2.0.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ecd6167b48bb8f1922fae7b49554616f2e7382748a4320ad46ebd7e2cc62f3d"}, 920 | {file = "pyerfa-2.0.0.3-cp39-cp39-win32.whl", hash = "sha256:7f9eabfefa5317ce58fe22480102902f10f270fc64a5636c010f7c0b7e0fb032"}, 921 | {file = "pyerfa-2.0.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:4ea7ca03ecc440224c2bed8fb136fadf6cf8aea8ba67d717f635116f30c8cc8c"}, 922 | {file = "pyerfa-2.0.0.3.tar.gz", hash = "sha256:d77fbbfa58350c194ccb99e5d93aa05d3c2b14d5aad8b662d93c6ad9fff41f39"}, 923 | ] 924 | 925 | [package.dependencies] 926 | numpy = ">=1.17" 927 | 928 | [package.extras] 929 | docs = ["sphinx-astropy (>=1.3)"] 930 | test = ["pytest", "pytest-doctestplus (>=0.7)"] 931 | 932 | [[package]] 933 | name = "pygments" 934 | version = "2.15.1" 935 | description = "Pygments is a syntax highlighting package written in Python." 936 | optional = false 937 | python-versions = ">=3.7" 938 | files = [ 939 | {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, 940 | {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, 941 | ] 942 | 943 | [package.extras] 944 | plugins = ["importlib-metadata"] 945 | 946 | [[package]] 947 | name = "pyparsing" 948 | version = "3.0.9" 949 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 950 | optional = false 951 | python-versions = ">=3.6.8" 952 | files = [ 953 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 954 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 955 | ] 956 | 957 | [package.extras] 958 | diagrams = ["jinja2", "railroad-diagrams"] 959 | 960 | [[package]] 961 | name = "python-dateutil" 962 | version = "2.8.2" 963 | description = "Extensions to the standard Python datetime module" 964 | optional = false 965 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 966 | files = [ 967 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 968 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 969 | ] 970 | 971 | [package.dependencies] 972 | six = ">=1.5" 973 | 974 | [[package]] 975 | name = "pytoolconfig" 976 | version = "1.2.5" 977 | description = "Python tool configuration" 978 | optional = false 979 | python-versions = ">=3.7" 980 | files = [ 981 | {file = "pytoolconfig-1.2.5-py3-none-any.whl", hash = "sha256:239ba9d3e537b91d0243275a497700ea39a5e259ddb80421c366e3b288bf30fe"}, 982 | {file = "pytoolconfig-1.2.5.tar.gz", hash = "sha256:a50f9dfe23b03a9d40414c1fdf902fefbeae12f2ac75a3c8f915944d6ffac279"}, 983 | ] 984 | 985 | [package.dependencies] 986 | packaging = ">=22.0" 987 | platformdirs = {version = ">=1.4.4", optional = true, markers = "extra == \"global\""} 988 | tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} 989 | 990 | [package.extras] 991 | doc = ["sphinx (>=4.5.0)", "tabulate (>=0.8.9)"] 992 | gendocs = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] 993 | global = ["platformdirs (>=1.4.4)"] 994 | validation = ["pydantic (>=1.7.4)"] 995 | 996 | [[package]] 997 | name = "pyyaml" 998 | version = "6.0" 999 | description = "YAML parser and emitter for Python" 1000 | optional = false 1001 | python-versions = ">=3.6" 1002 | files = [ 1003 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1004 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1005 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1006 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1007 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 1008 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1009 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1010 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 1011 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 1012 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 1013 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 1014 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 1015 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 1016 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 1017 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1018 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1019 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1020 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 1021 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1022 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1023 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1024 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1025 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1026 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 1027 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1028 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1029 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1030 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1031 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1032 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 1033 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1034 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1035 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1036 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1037 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1038 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1039 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 1040 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1041 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1042 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "rope" 1047 | version = "1.8.0" 1048 | description = "a python refactoring library..." 1049 | optional = false 1050 | python-versions = ">=3.7" 1051 | files = [ 1052 | {file = "rope-1.8.0-py3-none-any.whl", hash = "sha256:0767424ed40ce237dcf1c1f5088054fef960e5b19f4a0850783a259a3600d7bd"}, 1053 | {file = "rope-1.8.0.tar.gz", hash = "sha256:3de1d1f1cf2412540c6a150067fe25298175e7c2b72455b6ca8395f61678da82"}, 1054 | ] 1055 | 1056 | [package.dependencies] 1057 | pytoolconfig = {version = ">=1.2.2", extras = ["global"]} 1058 | 1059 | [package.extras] 1060 | dev = ["build (>=0.7.0)", "pip-tools (>=6.12.1)", "pre-commit (>=2.20.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)", "toml (>=0.10.2)"] 1061 | doc = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] 1062 | 1063 | [[package]] 1064 | name = "scipy" 1065 | version = "1.9.3" 1066 | description = "Fundamental algorithms for scientific computing in Python" 1067 | optional = false 1068 | python-versions = ">=3.8" 1069 | files = [ 1070 | {file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"}, 1071 | {file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"}, 1072 | {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"}, 1073 | {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"}, 1074 | {file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"}, 1075 | {file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"}, 1076 | {file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"}, 1077 | {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"}, 1078 | {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"}, 1079 | {file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"}, 1080 | {file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"}, 1081 | {file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"}, 1082 | {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"}, 1083 | {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"}, 1084 | {file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"}, 1085 | {file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"}, 1086 | {file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"}, 1087 | {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"}, 1088 | {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"}, 1089 | {file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"}, 1090 | {file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"}, 1091 | ] 1092 | 1093 | [package.dependencies] 1094 | numpy = ">=1.18.5,<1.26.0" 1095 | 1096 | [package.extras] 1097 | dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] 1098 | doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] 1099 | test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] 1100 | 1101 | [[package]] 1102 | name = "setuptools" 1103 | version = "67.8.0" 1104 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 1105 | optional = false 1106 | python-versions = ">=3.7" 1107 | files = [ 1108 | {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, 1109 | {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, 1110 | ] 1111 | 1112 | [package.extras] 1113 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 1114 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 1115 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 1116 | 1117 | [[package]] 1118 | name = "six" 1119 | version = "1.16.0" 1120 | description = "Python 2 and 3 compatibility utilities" 1121 | optional = false 1122 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1123 | files = [ 1124 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1125 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "tomli" 1130 | version = "2.0.1" 1131 | description = "A lil' TOML parser" 1132 | optional = false 1133 | python-versions = ">=3.7" 1134 | files = [ 1135 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1136 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "traitlets" 1141 | version = "5.9.0" 1142 | description = "Traitlets Python configuration system" 1143 | optional = false 1144 | python-versions = ">=3.7" 1145 | files = [ 1146 | {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, 1147 | {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, 1148 | ] 1149 | 1150 | [package.extras] 1151 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] 1152 | test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] 1153 | 1154 | [[package]] 1155 | name = "typing-extensions" 1156 | version = "4.5.0" 1157 | description = "Backported and Experimental Type Hints for Python 3.7+" 1158 | optional = false 1159 | python-versions = ">=3.7" 1160 | files = [ 1161 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, 1162 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "uncertainties" 1167 | version = "3.1.7" 1168 | description = "Transparent calculations with uncertainties on the quantities involved (aka error propagation); fast calculation of derivatives" 1169 | optional = false 1170 | python-versions = "*" 1171 | files = [ 1172 | {file = "uncertainties-3.1.7-py2.py3-none-any.whl", hash = "sha256:4040ec64d298215531922a68fa1506dc6b1cb86cd7cca8eca848fcfe0f987151"}, 1173 | {file = "uncertainties-3.1.7.tar.gz", hash = "sha256:80111e0839f239c5b233cb4772017b483a0b7a1573a581b92ab7746a35e6faab"}, 1174 | ] 1175 | 1176 | [package.dependencies] 1177 | future = "*" 1178 | 1179 | [package.extras] 1180 | all = ["nose", "numpy", "sphinx"] 1181 | docs = ["sphinx"] 1182 | optional = ["numpy"] 1183 | tests = ["nose", "numpy"] 1184 | 1185 | [[package]] 1186 | name = "wcwidth" 1187 | version = "0.2.6" 1188 | description = "Measures the displayed width of unicode strings in a terminal" 1189 | optional = false 1190 | python-versions = "*" 1191 | files = [ 1192 | {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, 1193 | {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "zipp" 1198 | version = "3.15.0" 1199 | description = "Backport of pathlib-compatible object wrapper for zip files" 1200 | optional = false 1201 | python-versions = ">=3.7" 1202 | files = [ 1203 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 1204 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 1205 | ] 1206 | 1207 | [package.extras] 1208 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1209 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 1210 | 1211 | [metadata] 1212 | lock-version = "2.0" 1213 | python-versions = "^3.8" 1214 | content-hash = "07631c96fbaa8c372b20d57654d55458898310e66f3659152c67a3d1c8b12975" 1215 | --------------------------------------------------------------------------------