def __init__(self,x,y,xUnit,yUnit,params={}): ''' Inputs: x = wavelength/frequency values, 1d array-like y = spectral flux density, 1d array-like xUnit = astropy.units.Unit of physical type length or frequency yUnit = astropy.units.Unit of physical type luminosity/flux density /length or frequency ''' self.type = 'spectrum' # Validate x input wavelengths = np.asarray(x) spec = np.asarray(y) if wavelengths.ndim!=1: raise ValueError('Wavelength/Frequency must be given as a 1d array') if wavelengths.shape!=spec.shape: raise ValueError('Wavelength/Frequency array and flux density array have different shapes.') if u.get_physical_type(xUnit) not in ['frequency','length']: raise TypeError(xUnit,' is neither a unit of frequency nor a unit of length. Get it together.') wavelengths = wavelengths * xUnit wavelengths.to('micron',equivalencies=u.spectral()) if not np.all(np.ediff1d(wavelengths)>0.): if u.get_physical_type(xUnit) == 'frequency': raise ValueError('Frequencies must be monotonically decreasing.') else: raise ValueError('Wavelengths must be monotonically increasing.') self.wavelengths = wavelengths # Validate y input spec = np.asarray(y) if spec.ndim!=1: raise ValueError('Spectrum must be given as a 1d array') # Define desired flux units from base units uFnu = u.Unit('erg')/u.Unit('s')/u.Unit('Hz') uFnuN = u.Unit('erg')/u.Unit('s')/u.Unit('Hz')/u.Unit('cm')**2 # astropy equivalency only works when area uncluded uFlam = u.Unit('erg')/u.Unit('s')/u.Unit('Angstrom') uFlamN = u.Unit('erg')/u.Unit('s')/u.Unit('Angstrom')/u.Unit('cm')**2 # Need F_nu, but if there's no area, need to add because too lazy to add equivalency if yUnit.is_equivalent(uFnu): spec = spec * yUnit / (4*np.pi*((10 * u.Unit('parsec')).to('cm'))**2) #spec = spec * yUnit / u.Unit('m')**2 elif yUnit.is_equivalent(uFlam): spec = spec * yUnit/ (4*np.pi*((10 * u.Unit('parsec')).to('cm'))**2) #spec = spec * yUnit/ u.Unit('m')**2 else: spec = (spec * yUnit) . to(uFnuN) if not spec.unit.is_equivalent(uFlamN) and not spec.unit.is_equivalent(uFnuN): raise ValueError(spec.unit,' not recognized as a unit of spectral flux density.') spec = spec.to(uFnuN,equivalencies=u.spectral_density(wavelengths)) if wavelengths[-1]<wavelengths[0]: # then we originally had flux units wavelengths = np.flipud(wavelengths) spec = np.flipud(spec) self.spec = spec self.params = params
def _validate_unit_type(name, value, expected_unit): _validate_type(name, value, Quantity) if isinstance(expected_unit, Quantity): expected_unit = expected_unit.unit if au.get_physical_type(value.unit) != au.get_physical_type(expected_unit): raise ValueError("{name} units should be {expected_unit}, got {type}.".format(name=name, expected_unit=au.get_physical_type( expected_unit), type=au.get_physical_type( value.unit)))
def calc_model_lc(spectra, transient, filter): spectra_ = copy.deepcopy(spectra) spectra_ = spectra_.redshift(z=transient.redshift) spectra_ = spectra_.dust_extinction(Eb_v = transient.Eb_v, model="maeda") lc = photontools.calc_band_flux(spectra_, filter) if (u.get_physical_type((transient.luminosity_distance * u.m / u.m).unit) == "length"): lc = lc.convert_flux_to_magnitude(filter, system="AB", distance=transient.luminosity_distance) elif (u.get_physical_type((transient.luminosity_distance * u.m / u.m).unit) == "dimensionless"): lc = lc.convert_flux_to_magnitude(filter, system="AB", distance=transient.luminosity_distance * u.Mpc) else: raise ValueError("Input luminosity_distnace unit is wrong!") return lc
def __init__(self,x,xUnit,y,filtName): ''' x = values of frequency or wavelength at which FTC is defined, array-like xUnit = astropy.units.Unit of type lengthor frequency y = filter transmission at x, array of length (len(x)) ''' self.type='filter' self.name=filtName wavelengths = np.asarray(x) ftc = np.asarray(y) if x.ndim!=1: raise ValueError('Wavelength/Frequency must be given as a 1d array.') if wavelengths.shape!=ftc.shape: raise ValueError('Wavelength/Frequency array and transmission array have different shapes.') if u.get_physical_type(xUnit) not in ['frequency','length']: raise TypeError(xUnit,' is neither a unit of frequency nor a unit of length. Get it together.') # Convert wavelengths/frequencies to microns wavelengths = x * xUnit wavelengths = wavelengths.to('Angstrom',equivalencies=u.spectral()) # Eliminate any negative transmission values ftc = y ftc[y<0.]=0. # Reverse array orders if FTC was defined in frequency space if wavelengths[-1]<wavelengths[0]: wavelengths = np.flipud(wavelengths) ftc = np.flipud(ftc) self.wavelengths = wavelengths self.ftc = ftc
def __init__(self,ftcFile,unit,filtName): # Validate unit try: unitType = u.get_physical_type(u.Unit(unit)) except: os.system("say '"+unit+" is not a recognized unit. Don't waste my time "+name+"'") raise TypeError(unit," is not a recognized unit. Don't waste my time.") if unitType not in ['length','frequency']: os.system("say '"+unit+" is neither a unit of frequency nor a unit of length. Get it together "+str(name)+"'") raise TypeError(unit,' is neither a unit of frequency nor a unit of length. Get it together.') self.name = filtName # Load FTC if unitType == 'length': self.ftcLambda, self.ftcTransLam = np.loadtxt(ftcFile,unpack=True) self.ftcLambda = self.ftcLambda * u.Unit(unit) self.ftcLambda = self.ftcLambda.to(u.micron) self.ftcNu = const.c / self.ftcLambda self.ftcNu = self.ftcNu.to(u.Hz) self.ftcNu = self.ftcNu[::-1] self.ftcTransLam[self.ftcTransLam<0.] = 0. self.ftcTransNu = self.ftcTransLam[::-1] if unitType == 'frequency': self.ftcNu, self.ftcTransNu = np.loadtxt(ftcFile,unpack=True) self.ftcNu = self.ftcNu * u.Unit(unit) self.ftcNu = self.ftcNu.to(u.Hz) self.ftcLambda = const.c / self.ftcNu self.ftcLambda = self.ftcLambda.to(u.AA) self.ftcTransNu[self.ftcTransNu<0.] = 0. self.ftcTransLam = self.ftcTransNu[::-1]
def indices_xfind_coords( coords1, coords2, maxdist=1 * u.arcsec, obstime=None, ): """Indices of X-Find coords. Returns ------- idx1 : integer array idx2 : integer array indices into `other` for all coordinates which have a "match" in `catalog`. info : dict Useful information # TODO See Also -------- :func:`~astropy.coordinates.search_around_sky` :func:`~astropy.coordinates.search_around_3d` """ if u.get_physical_type(maxdist.unit) == "angle": idx1, idx2, sep2d, dist3d = coord.search_around_sky( coords1, coords2, seplimit=maxdist, ) elif u.get_physical_type(maxdist.unit) == "length": idx1, idx2, sep2d, dist3d = coord.search_around_3d( coords1, coords2, distlimit=maxdist, ) info = {"sep2d": sep2d, "dist3d": dist3d} return idx1, idx2, info
def __init__(self, ftcFile, unit, filtName): # Validate unit try: unitType = u.get_physical_type(u.Unit(unit)) except: os.system("say '" + unit + " is not a recognized unit. Don't waste my time " + name + "'") raise TypeError(unit, " is not a recognized unit. Don't waste my time.") if unitType not in ['length', 'frequency']: os.system( "say '" + unit + " is neither a unit of frequency nor a unit of length. Get it together " + str(name) + "'") raise TypeError( unit, ' is neither a unit of frequency nor a unit of length. Get it together.' ) self.name = filtName # Load FTC if unitType == 'length': self.ftcLambda, self.ftcTransLam = np.loadtxt(ftcFile, unpack=True) self.ftcLambda = self.ftcLambda * u.Unit(unit) self.ftcLambda = self.ftcLambda.to(u.micron) self.ftcNu = const.c / self.ftcLambda self.ftcNu = self.ftcNu.to(u.Hz) self.ftcNu = self.ftcNu[::-1] self.ftcTransLam[self.ftcTransLam < 0.] = 0. self.ftcTransNu = self.ftcTransLam[::-1] if unitType == 'frequency': self.ftcNu, self.ftcTransNu = np.loadtxt(ftcFile, unpack=True) self.ftcNu = self.ftcNu * u.Unit(unit) self.ftcNu = self.ftcNu.to(u.Hz) self.ftcLambda = const.c / self.ftcNu self.ftcLambda = self.ftcLambda.to(u.AA) self.ftcTransNu[self.ftcTransNu < 0.] = 0. self.ftcTransLam = self.ftcTransNu[::-1]
def __init__(self, x, xUnit, y, filtName): ''' x = values of frequency or wavelength at which FTC is defined, array-like xUnit = astropy.units.Unit of type lengthor frequency y = filter transmission at x, array of length (len(x)) ''' self.type = 'filter' self.name = filtName wavelengths = np.asarray(x) ftc = np.asarray(y) if x.ndim != 1: raise ValueError( 'Wavelength/Frequency must be given as a 1d array.') if wavelengths.shape != ftc.shape: raise ValueError( 'Wavelength/Frequency array and transmission array have different shapes.' ) if u.get_physical_type(xUnit) not in ['frequency', 'length']: raise TypeError( xUnit, ' is neither a unit of frequency nor a unit of length. Get it together.' ) # Convert wavelengths/frequencies to microns wavelengths = x * xUnit wavelengths = wavelengths.to('Angstrom', equivalencies=u.spectral()) # Eliminate any negative transmission values ftc = y ftc[y < 0.] = 0. # Reverse array orders if FTC was defined in frequency space if wavelengths[-1] < wavelengths[0]: wavelengths = np.flipud(wavelengths) ftc = np.flipud(ftc) self.wavelengths = wavelengths self.ftc = ftc
def _get_physical_type_dict( iterable: Iterable, *, only_quantities=False, numbers_become_quantities=False, ) -> Dict[u.PhysicalType, u.Quantity]: """ Return a `dict` that contains `~astropy.units.PhysicalType` objects as keys and the corresponding objects in ``iterable`` as values. Objects in ``iterable`` that do not correspond to a |PhysicalType| are skipped. Parameters ---------- iterable : iterable A iterable that is expected to contain objects with physical types. only_quantities : `bool`, keyword-only, optional If `True`, only `~astropy.units.Quantity` instances in ``iterable`` will be passed into the resulting `dict`. If `False`, then any unit, |PhysicalType|, or object that can be converted to a |Quantity| or that has a physical type will be included in the `dict`. Defaults to `False`. numbers_become_quantities : `bool`, keyword-only, optional If `True`, `~numbers.Number` objects will be converted into dimensionless |Quantity| instances. If `False`, `~numbers.Number` objects will be skipped. Defaults to `False`. Returns ------- physical_types : `dict` A mapping from |PhysicalType| instances to the corresponding objects in ``iterable``. Examples -------- >>> import astropy.units as u >>> from plasmapy.utils.units_helpers import _get_physical_type_dict >>> quantities = [1 * u.m, 2 * u.kg] >>> _get_physical_type_dict(quantities) {PhysicalType('length'): <Quantity 1. m>, PhysicalType('mass'): <Quantity 2. kg>} """ physical_types = {} for obj in iterable: if isinstance(obj, Number) and numbers_become_quantities: obj = u.Quantity(obj, u.dimensionless_unscaled) if only_quantities and not isinstance(obj, u.Quantity): continue try: physical_type = u.get_physical_type(obj) except (TypeError, ValueError): pass else: if physical_type in physical_types: raise ValueError(f"Duplicate physical type: {physical_type}") physical_types[physical_type] = obj return physical_types
def create_model(line_centers, amp_guess=None, center_guess=None, width_guess=None, center_limits=None, width_limits=None, center_fixed=None, width_fixed=None, lambda_units=u.micron): """ Function that allows for the creation of a generic model for a spectral region. Each line specified in 'line_names' must be included in the file 'lines.py'. Defaults for the amplitude guesses will be 1.0 for all lines. Defaults for the center guesses will be the observed wavelengths. Defaults for the line widths will be 100 km/s for narrow lines and 1000 km/s for the broad lines. All lines are considered narrow unless the name has 'broad' attached to the end of the name. """ nlines = len(line_centers.keys()) line_names = line_centers.keys() # Create the default amplitude guesses for the lines if necessary if amp_guess is None: amp_guess = {l: 1.0 for l in line_names} # Create arrays to hold the default line center and width guesses if center_guess is None: center_guess = {l: 0*u.km/u.s for l in line_names} if width_guess is None: width_guess = {l: 100.*u.km/u.s for l in line_names} # Loop through each line and create a model mods = [] for i, l in enumerate(line_names): # Equivalency to convert to/from wavelength from/to velocity opt_conv = u.doppler_optical(line_centers[l]) # Convert the guesses for the line center and width to micron center_guess_i = center_guess[l].to(lambda_units, equivalencies=opt_conv) if u.get_physical_type(width_guess[l].unit) == 'speed': width_guess_i = (width_guess[l].to(lambda_units, equivalencies=u.doppler_optical(center_guess_i)) - center_guess_i) elif u.get_physical_type(width_guess[l].unit) == 'length': width_guess_i = width_guess[i].to(lambda_units) center_guess_i = center_guess_i.value width_guess_i = width_guess_i.value # Create the single Gaussian line model for the emission line mod_single = apy_mod.models.Gaussian1D(mean=center_guess_i, amplitude=amp_guess[l], stddev=width_guess_i, name=l) # Set the constraints on the parameters if necessary mod_single.amplitude.min = 0 # always an emission line if center_limits is not None: if center_limits[l][0] is not None: mod_single.mean.min = center_limits[l][0].to(lambda_units, equivalencies=opt_conv).value if center_limits[l][1] is not None: mod_single.mean.max = center_limits[l][1].to(lambda_units, equivalencies=opt_conv).value if width_limits is not None: if width_limits[l][0] is not None: mod_single.stddev.min = width_limits[l][0].to(lambda_units, equivalencies=opt_conv).value - line_centers[l].value else: mod_single.stddev.min = 0 # can't have negative width if width_limits[l][1] is not None: mod_single.stddev.max = width_limits[l][1].to(lambda_units, equivalencies=opt_conv).value - line_centers[l].value else: mod_single.stddev.min = 0 # Set the fixed parameters if center_fixed is not None: mod_single.mean.fixed = center_fixed[l] if width_fixed is not None: mod_single.stddev.fixed = width_fixed[l] # Add to the model list mods.append(mod_single) # Create the combined model by adding all of the models together if nlines == 1: final_model = mods[0] else: final_model = mods[0] for m in mods[1:]: final_model += m return final_model
def indices_xmatch_coords( catalog, other, maxdist=1 * u.arcsec, obstime=None, nthneighbor: int = 1, ): """Basic 2-catalog x-match. Returns match indices. see https://docs.astropy.org/en/stable/coordinates/matchsep.html Parameters ---------- catalog, other : SkyCoord or BaseCoordinateFrame `catalog` is the "catalogcoord", `other` are the "matchcoord". Note that this is in the opposite order as Astropy. maxdist : `~astropy.units.Angle` or `~astropy.coordinates.Distance`, optional The maximum separation to be considered a match. If Angle, does an on-sky x-match using :func:`~astropy.coordinates.match_coordinates_sky`. If Distance, does a 3d x-match using :func:`~astropy.coordinates.match_coordinates_3d`. obstime : Time, optional If provided, the "epoch" at which to x-match the coords. If None (default), will use the obstime of the first catalog to have an obstime. An absence of obstime in a catalog means the coordinates are *right now*, if this is not the case, ensure the catalog has this information. Returns ------- catalog_idx : integer array indices into `catalog` for the x-match. info : dict Useful information. - sep2d : on-sky separation (Angle) - dist3d : 3D distance (Quantity) Other Parameters ---------------- nthneighbor : int The nthneighbor to use in ``match_coordinates_``. TODO, rename to "_nth" but "quantity_input" can't handle underscores. See Also -------- :func:`~astropy.coordinates.match_coordinates_sky` :func:`~astropy.coordinates.match_coordinates_3d` """ if u.get_physical_type(maxdist.unit) == "angle": idx, sep2d, dist3d = coord.match_coordinates_sky( matchcoord=other, catalogcoord=catalog, nthneighbor=nthneighbor, ) sep = sep2d # separation constraints on this elif u.get_physical_type(maxdist.unit) == "length": idx, sep2d, dist3d = coord.match_coordinates_3d( matchcoord=other, catalogcoord=catalog, nthneighbor=nthneighbor, ) sep = dist3d # separation constraints on this # separation constraints midx = np.where(sep < maxdist)[0] if len(midx) == 0: # no matches return False, False, {} elif len(midx) == 1: # correct for case of only 1 match midx = midx[0] sel = ... else: sel = midx catalog_idx = idx[sel] other_idx = midx info = {"sep2d": sep2d[sel], "dist3d": dist3d[sel]} return catalog_idx, other_idx, info
un.add_enabled_units([littleh]) class UnitError(ValueError): """An error pertaining to having incorrect units.""" pass Length = un.Quantity["length"] Meters = un.Quantity["m"] Time = un.Quantity["time"] Frequency = un.Quantity["frequency"] Temperature = un.Quantity["temperature"] TempSquared = un.Quantity[un.get_physical_type("temperature")**2] Wavenumber = un.Quantity[littleh / un.Mpc] Delta = un.Quantity[un.mK**2] time_as_distance = [( un.s, un.m, lambda x: cnst.c.to_value("m/s") * x, lambda x: x / cnst.c.to_value("m/s"), )] def vld_physical_type(unit: str) -> Callable[[Any, attr.Attribute, Any], None]: """Attr validator to check physical type.""" def _check_type(self: Any, att: attr.Attribute, val: Any): if not isinstance(val, un.Quantity):
from plasmapy.utils.units_helpers import _get_physical_type_dict def test_get_physical_type_dict_specific_example(): units = [u.m, u.m**-3, u.m * u.s] quantities = [5 * unit for unit in units] expected = { quantity.unit.physical_type: quantity for quantity in quantities } new_physical_type_dict = _get_physical_type_dict(quantities) assert new_physical_type_dict == expected velocity = u.get_physical_type("velocity") mass = u.get_physical_type("mass") dimensionless = u.get_physical_type(1) test_case = namedtuple("case", ["collection", "kwargs", "expected"]) test_cases = [ test_case( collection=(c, m_e), kwargs={}, expected={ velocity: c, mass: m_e }, ), test_case(
def check_param_values(model, **user_param): """Performs generic check that the requested model parameters have valid values and units for the requested SNEWPY model. Parameters ---------- model : snewpy.model.SupernovaModel Model class used to perform parameter check user_param : varies User-requested model parameters to be tested for validity. MUST be provided as keyword arguments that match the model `param` class member Raises ------ ValueError If invalid model parameters are provided based on units, allowed values, etc. UnitTypeError If invalid units are provided for a model parameter See Also -------- snewpy.models.ccsn snewpy.models.presn """ model_param = model.param # Check that the appropriate number of params are provided if len(user_param) != len(model_param): raise ValueError( f"Invalid model parameters, expected {len(model_param)} " f"but {len(user_param)} were given") # Check that user-requested params have valid units and values for (key, allowed_params), user_param in zip(model_param.items(), user_param.values()): # If both have units, check that the user param value is valid. If valid, continue. Else, error if type(user_param) == Quantity and type(allowed_params) == Quantity: if get_physical_type(user_param.unit) != get_physical_type( allowed_params.unit): raise UnitTypeError( f"Incorrect units {user_param.unit} provided for parameter {key}, " f"expected {allowed_params.unit}") elif user_param.to( allowed_params.unit).value in allowed_params.value: continue else: raise ValueError( f"Invalid value '{user_param}' provided for parameter {key}, " f"allowed value(s): {allowed_params}") # If one only one has units, then error elif (type(user_param) == Quantity) ^ (type(allowed_params) == Quantity): # User param has units, model param is unitless if type(user_param) == Quantity: raise ValueError( f"Invalid units {user_param.unit} for parameter {key} provided, expected None" ) else: raise ValueError( f"Missing units for parameter {key}, expected {allowed_params.unit}" ) # Check that unitless user param value is valid. If valid, continue. Else, Error elif user_param in allowed_params: continue else: raise ValueError( f"Invalid value '{user_param}' provided for parameter {key}, " f"allowed value(s): {allowed_params}")
def grid_baselines( self, baselines: tp.Length | None = None, weights: np.ndarray | None = None, integration_time: tp.Time = 60.0 * un.s, bl_min: tp.Length = 0 * un.m, bl_max: tp.Length = np.inf * un.m, observation_duration: tp.Time | None = None, ndecimals: int = 1, ) -> np.ndarray: """ Grid baselines onto a pre-determined uvgrid, accounting for earth rotation. Parameters ---------- baselines : array_like, optional The baseline co-ordinates to project, assumed to be in metres. If not provided, calculates effective baselines by finding redundancies on all baselines in the observatory. Shape of the array can be (N,N,3) or (N, 3). The co-ordinates are expected to be in ENU. If `baselines` is provided, `weights` must also be provided. weights: array_like, optional An array of the same length as `baselines`, giving the number of independent baselines at each co-ordinate. If not provided, calculates effective baselines by finding redundancies on all baselines in the observatory. If `baselines` is provided, `weights` must also be provided. integration_time : float or Quantity, optional The amount of time integrated into a snapshot visibility, assumed to be in seconds. bl_min : float or Quantity, optional Minimum baseline length (in meters) to include in the gridding. bl_max : float or Quantity, optional Maximum baseline length (in meters) to include in the gridding. observation_duration : float or Quantity, optional Amount of time in a single (coherent) LST bin, assumed to be in minutes. ndecimals : int, optional Number of decimals to which baselines must match to be considered redundant. Returns ------- array : Shape [n_baseline_groups, Nuv, Nuv]. The coherent sum of baselines within grid cells given by :attr:`ugrid`. One can treat different baseline groups independently, or sum over them. See Also -------- grid_baselines_coherent : Coherent sum over baseline groups of the output of this method. grid_basleine_incoherent : Incoherent sum over baseline groups of the output of this method. """ if baselines is not None: assert un.get_physical_type(baselines) == "length" assert baselines.ndim in (2, 3) assert un.get_physical_type(integration_time) == "time" assert un.get_physical_type(bl_min) == "length" assert un.get_physical_type(bl_max) == "length" if observation_duration is not None: assert un.get_physical_type(observation_duration) == "time" if baselines is None: baseline_groups = self.get_redundant_baselines(bl_min=bl_min, bl_max=bl_max, ndecimals=ndecimals) baselines = self.baseline_coords_from_groups(baseline_groups) weights = self.baseline_weights_from_groups(baseline_groups) if weights is None: raise ValueError( "If baselines are provided, weights must also be provided.") time_offsets = self.time_offsets_from_obs_int_time( integration_time, observation_duration) uvws = self.projected_baselines(baselines, time_offsets).reshape( baselines.shape[0], time_offsets.size, 3) # grid each baseline type into uv plane dim = len(self.ugrid(bl_max)) edges = self.ugrid_edges(bl_max) uvsum = np.zeros((len(baselines), dim, dim)) for cnt, (uvw, nbls) in enumerate( tqdm.tqdm( zip(uvws, weights), desc="gridding baselines", unit="baselines", disable=not config.PROGRESS, total=len(weights), )): uvsum[cnt] = np.histogram2d(uvw[:, 0], uvw[:, 1], bins=edges)[0] * nbls return uvsum
def test_pickling(ptype_name): # Regression test for #11685 ptype = u.get_physical_type(ptype_name) pkl = pickle.dumps(ptype) other = pickle.loads(pkl) assert other == ptype
def __init__(self, x, y, xUnit, yUnit, params={}): ''' Inputs: x = wavelength/frequency values, 1d array-like y = spectral flux density, 1d array-like xUnit = astropy.units.Unit of physical type length or frequency yUnit = astropy.units.Unit of physical type luminosity/flux density /length or frequency ''' self.type = 'spectrum' # Validate x input wavelengths = np.asarray(x) spec = np.asarray(y) if wavelengths.ndim != 1: raise ValueError( 'Wavelength/Frequency must be given as a 1d array') if wavelengths.shape != spec.shape: raise ValueError( 'Wavelength/Frequency array and flux density array have different shapes.' ) if u.get_physical_type(xUnit) not in ['frequency', 'length']: raise TypeError( xUnit, ' is neither a unit of frequency nor a unit of length. Get it together.' ) wavelengths = wavelengths * xUnit wavelengths.to('micron', equivalencies=u.spectral()) if not np.all(np.ediff1d(wavelengths) > 0.): if u.get_physical_type(xUnit) == 'frequency': raise ValueError( 'Frequencies must be monotonically decreasing.') else: raise ValueError( 'Wavelengths must be monotonically increasing.') self.wavelengths = wavelengths # Validate y input spec = np.asarray(y) if spec.ndim != 1: raise ValueError('Spectrum must be given as a 1d array') # Define desired flux units from base units uFnu = u.Unit('erg') / u.Unit('s') / u.Unit('Hz') uFnuN = u.Unit('erg') / u.Unit('s') / u.Unit('Hz') / u.Unit( 'cm')**2 # astropy equivalency only works when area uncluded uFlam = u.Unit('erg') / u.Unit('s') / u.Unit('Angstrom') uFlamN = u.Unit('erg') / u.Unit('s') / u.Unit('Angstrom') / u.Unit( 'cm')**2 # Need F_nu, but if there's no area, need to add because too lazy to add equivalency if yUnit.is_equivalent(uFnu): spec = spec * yUnit / (4 * np.pi * ((10 * u.Unit('parsec')).to('cm'))**2) #spec = spec * yUnit / u.Unit('m')**2 elif yUnit.is_equivalent(uFlam): spec = spec * yUnit / (4 * np.pi * ((10 * u.Unit('parsec')).to('cm'))**2) #spec = spec * yUnit/ u.Unit('m')**2 else: spec = (spec * yUnit).to(uFnuN) if not spec.unit.is_equivalent(uFlamN) and not spec.unit.is_equivalent( uFnuN): raise ValueError( spec.unit, ' not recognized as a unit of spectral flux density.') spec = spec.to(uFnuN, equivalencies=u.spectral_density(wavelengths)) if wavelengths[-1] < wavelengths[ 0]: # then we originally had flux units wavelengths = np.flipud(wavelengths) spec = np.flipud(spec) self.spec = spec self.params = params