Skip to content

Commit

Permalink
Some more tweaks.
Browse files Browse the repository at this point in the history
  • Loading branch information
ajkeller34 committed Oct 20, 2017
1 parent 007362d commit c31e249
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 58 deletions.
23 changes: 16 additions & 7 deletions docs/src/logarithm.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ julia> u"dB"*3 === 3u"dB"
true
```

Currently implemented are `dB`, `dBm`, `dBV`, `dBu`, `dBμV`, `dBSPL`, `Np`.
Currently implemented are `dB`, `B`, `dBm`, `dBV`, `dBu`, `dBμV`, `dBSPL`, `cNp`, `Np`.

One can also construct logarithmic quantities using the `@dB` or `@Np` macros to use
an arbitrary reference level:
One can also construct logarithmic quantities using the `@dB`, `@B`, `@cNp`, `@Np` macros to
use an arbitrary reference level:

```jldoctest
julia> using Unitful: mW, V
Expand All @@ -46,6 +46,16 @@ julia> @Np e*V/V # e = 2.71828...
1.0 Np (1 V)
```

When using the macros, the levels are constructed at parse time. The scales themselves are
callable as functions if you need to construct a level that way:

```jldoctest
julia> using Unitful: dB, mW, V
julia> u"dB"(10mW,mW)
10.0 dBm
```

In calculating the logarithms, the log function appropriate to the scale in question is used
(`log10` for decibels, `log` for nepers).

Expand Down Expand Up @@ -463,13 +473,12 @@ Unitful.@_doctables u"3dBm"*u"3dBm"
</tbody>
</table>

‡: `1/Hz * 3dB` is technically allowed but dumb things can happen when its unclear if a quantity
is a root-power or power quantity:
‡: `1/Hz * 3dB` could be allowed, technically, but we throw an error its unclear if a
quantity is a root-power or power quantity:

```jldoctest
julia> u"1/Hz" * u"3dB"
WARNING: result may be incorrect. Define `Unitful.isrootpower(::Type{<:Unitful.LogInfo}, ::typeof(𝐓))` to fix.
1.9952623149688795 Hz^-1
ERROR: undefined behavior. Please file an issue with the code needed to reproduce.
```

On the other hand, if it can be determined that a power quantity or root-power quantity
Expand Down
51 changes: 24 additions & 27 deletions src/logarithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Base.convert(::Type{LogScaled{L1}}, x::Level{L2,S}) where {L1,L2,S} = Level{L1,S
Base.convert(T::Type{<:Level}, x::Level) = T(x.val)

"""
reflevel(x::Level{L,S}) where {L,S} = S
reflevel(::Type{Level{L,S}}) where {L,S} = S
reflevel(::Type{Level{L,S,T}}) where {L,S,T} = S
reflevel(x::Level{L,S})
reflevel(::Type{Level{L,S}})
reflevel(::Type{Level{L,S,T}})
Returns the reference level, e.g.
```jldoctest
Expand Down Expand Up @@ -51,17 +51,19 @@ Base.convert(T::Type{Gain{L1,T1}}, x::Gain{L2,T2}) where {L1,L2,T1,T2} = T(_gcon
Base.convert(::Type{LogScaled{L1}}, x::Gain{L2}) where {L1,L2} = Gain{L1}(_gconv(L1,L2,x))
function _gconv(L1,L2,x)
if isrootpower(L1) == isrootpower(L2)
gain = tolog(L1,1,fromlog(L2,1,x.val))
gain = tolog(L1,fromlog(L2,x.val))
elseif isrootpower(L1) && !isrootpower(L2)
gain = tolog(L1,1,fromlog(L2,1,0.5*x.val))
gain = tolog(L1,fromlog(L2,0.5*x.val))
else
gain = tolog(L1,1,fromlog(L2,1,2*x.val))
gain = tolog(L1,fromlog(L2,2*x.val))
end
return gain
end

tolog(L,S,x) = (1+isrootpower(L,dimension(S))) * prefactor(L()) * (logfn(L()))(x)
fromlog(L,S,x) = S * expfn(L())( x / ((1+isrootpower(L,dimension(S)))*prefactor(L())) )
tolog(L,S,x) = (1+isrootpower(L,S)) * prefactor(L()) * (logfn(L()))(x)
tolog(L,x) = (1+isrootpower(L)) * prefactor(L()) * (logfn(L()))(x)
fromlog(L,S,x) = S * expfn(L())( x / ((1+isrootpower(L,S))*prefactor(L())) )
fromlog(L,x) = expfn(L())( x / ((1+isrootpower(L))*prefactor(L())) )

function Base.show(io::IO, x::MixedUnits{T,U}) where {T,U}
print(io, abbr(x))
Expand Down Expand Up @@ -113,19 +115,9 @@ end
ustrip(x::Level{L,S}) where {L<:LogInfo, S} = tolog(L,S,x.val/reflevel(x))
ustrip(x::Gain) = x.val

# TODO: some more dimensions?
isrootpower(x,y) = isrootpower_warn(x,y)

# Default to power or root-power as appropriate for the given logarithmic unit
function isrootpower_warn(x,y)
irp = isrootpower(x)
str = ifelse(irp, "root-power", "power")
warn("result may be incorrect. Define ",
"`Unitful.isrootpower(::Type{<:Unitful.LogInfo}, ::typeof($y))` to fix.")
return irp
end

isrootpower(t::Type{<:LogInfo}, ::typeof(NoDims)) = isrootpower(t)
isrootpower(T::Type{<:LogInfo}, y) = isrootpower_dim(T, dimension(y))
isrootpower_dim(::Type{<:LogInfo}, y) =
error("undefined behavior. Please file an issue with the code needed to reproduce.")

==(x::Gain, y::Level) = ==(y,x)
==(x::Level, y::Gain) = false
Expand Down Expand Up @@ -155,10 +147,11 @@ Base. *(x::Bool, y::Gain) = *(y,x) # for met
Base. *(x::Gain{L}, y::Number) where {L} = Gain{L}(x.val * y)
Base. *(x::Gain{L}, y::Bool) where {L} = Gain{L}(x.val * y) # for method ambiguity
Base. *(x::Gain{L}, y::Level) where {L} = Level{L,S}(fromlog(L, S, ustrip(x)+y.val))
Base. *(x::Gain{L}, y::Gain) where {L} = error("logarithmic gains add, not multiply.")
Base. *(x::Gain{L}, y::Gain) where {L} = *(promote(x,y)...)
Base. *(x::Gain{L}, y::Gain{L}) where {L} = Gain{L}(x.val + y.val) # contentious?

Base. *(x::Quantity, y::Gain{L}) where {L} =
isrootpower(L, dimension(x)) ? rootpowerratio(y) * x : powerratio(y) * x
isrootpower(L, x) ? rootpowerratio(y) * x : powerratio(y) * x
Base. *(x::Gain, y::Quantity) = *(y,x)

# Division
Expand All @@ -167,6 +160,10 @@ Base. /(x::Level{L,S}, y::Number) where {L,S} = Level{L,S}(x.val / y)
Base. /(x::Level{L,S}, y::Quantity) where {L,S} = x.val / y
Base. /(x::Level{L,S}, y::Level) where {L,S} = x.val / y.val
Base. /(x::Level{L,S}, y::Gain) where {L,S} = Level{L,S}(fromlog(L, S, ustrip(x) - y.val))

Base. /(x::Gain{L}, y::Gain) where {L} = /(promote(x,y)...)
Base. /(x::Gain{L}, y::Gain{L}) where {L} = Gain{L}(x.val - y.val)

Base. /(x::Quantity, y::Gain) = error("logarithmic gains subtract, not divide.")
Base. /(x::Quantity, y::Level) = x / y.val

Expand Down Expand Up @@ -234,11 +231,11 @@ exponential attenuation.
function powerratio end
powerratio(x) = powerratio(NoUnits, x)
powerratio(::Units{()}, x::Gain{L}) where {L} =
fromlog(L, 1, ifelse(isrootpower(L), 2, 1)*x.val)
fromlog(L, ifelse(isrootpower(L), 2, 1)*x.val)
powerratio(::Units{()}, x::Real) = x
powerratio(u::MixedUnits{<:Gain}, x::Gain) = uconvert(u, x)
powerratio(u::T, x::Real) where {L, T <: MixedUnits{Gain{L}, <:Units{()}}} =
ifelse(isrootpower(L), 0.5, 1) * tolog(L, 1, x) * u
ifelse(isrootpower(L), 0.5, 1) * tolog(L, x) * u

"""
rootpowerratio(x::Gain)
Expand All @@ -262,11 +259,11 @@ exponential attenuation.
function rootpowerratio end
rootpowerratio(x) = rootpowerratio(NoUnits, x)
rootpowerratio(::Units{()}, x::Gain{L}) where {L} =
fromlog(L, 1, ifelse(isrootpower(L), 1.0, 0.5)*x.val)
fromlog(L, ifelse(isrootpower(L), 1.0, 0.5)*x.val)
rootpowerratio(::Units{()}, x::Real) = x
rootpowerratio(u::MixedUnits{<:Gain}, x::Gain) = uconvert(u, x)
rootpowerratio(u::T, x::Real) where {L, T <: MixedUnits{Gain{L}, <:Units{()}}} =
ifelse(isrootpower(L), 1, 2) * tolog(L, 1, x) * u
ifelse(isrootpower(L), 1, 2) * tolog(L, x) * u

fieldratio = rootpowerratio

Expand Down
20 changes: 9 additions & 11 deletions src/pkgdefaults.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,26 +166,24 @@ Unitful.offsettemp(::Unitful.Unit{:Fahrenheit}) = 45967//100
#########
# Logarithmic scales and units

@logscale dB "dB" Decibel 10 10
@logscale B "B" Bel 10 1
@logscale Np "Np" Neper e 1//2
@logscale cNp "cNp" Centineper e 50
@logscale dB "dB" Decibel 10 10 false
@logscale B "B" Bel 10 1 false
@logscale Np "Np" Neper e 1//2 true
@logscale cNp "cNp" Centineper e 50 true

@logunit dBm "dBm" Decibel 1mW
@logunit dBV "dBV" Decibel 1V
@logunit dBu "dBu" Decibel sqrt(0.6)V
@logunit dBμV "dBμV" Decibel 1μV
@logunit dBSPL "dBSPL" Decibel 20μPa
@logunit dBFS "dBFS" Decibel 1

const dBµV = dBμV # different character encoding of μ

isrootpower(::Type{<:LogInfo}, ::typeof(dimension(W))) = false
isrootpower(::Type{<:LogInfo}, ::typeof(dimension(V))) = true
isrootpower(::Type{<:LogInfo}, ::typeof(dimension(A))) = true
isrootpower(::Type{<:LogInfo}, ::typeof(dimension(Pa))) = true
isrootpower(::Type{Decibel}) = false
isrootpower(::Type{Neper}) = true
# TODO: some more dimensions?
isrootpower_dim(::Type{<:LogInfo}, ::typeof(dimension(W))) = false
isrootpower_dim(::Type{<:LogInfo}, ::typeof(dimension(V))) = true
isrootpower_dim(::Type{<:LogInfo}, ::typeof(dimension(A))) = true
isrootpower_dim(::Type{<:LogInfo}, ::typeof(dimension(Pa))) = true

#########

Expand Down
1 change: 1 addition & 0 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,4 @@ struct MixedUnits{T<:LogScaled, U<:Units}
end
MixedUnits{T}() where {T} = MixedUnits{T, typeof(NoUnits)}(NoUnits)
MixedUnits{T}(u::Units) where {T} = MixedUnits{T,typeof(u)}(u)
(y::MixedUnits)(x::Number) = uconvert(y,x)
30 changes: 18 additions & 12 deletions src/user.jl
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ base SI units.
@inline upreferred(x::FixedUnits) = x

"""
@logscale(symb,abbr,name,base,prefactor)
@logscale(symb,abbr,name,base,prefactor,irp)
Define a logarithmic scale. Unlike with units, there is no special treatment for
power-of-ten prefixes (decibels and bels are defined separately). However, arbitrary
bases are possible, and computationally appropriate `log` and `exp` functions are used
Expand All @@ -307,12 +307,19 @@ This macro also defines another macro available as `@symb`. For example, `@dB` i
of decibels. This can be used to construct `Level` objects at parse time. Usage is like
`@dB 3V/1V`.
Note that `prefactor` is defined with respect to taking ratios of power quantities. As
usual, just divide by two if you want to refer to root-power / field quantities instead.
`prefactor` is the prefactor out in front of the logarithm for this log scale.
In all cases it is defined with respect to taking ratios of power quantities. Just divide
by two if you want to refer to root-power / field quantities instead.
`irp` (short for "is root power?") specifies whether the logarithmic scale is defined
with respect to ratios of power or root-power quantities. In short: use `false` if your scale
is decibel-like, or `true` if your scale is neper-like.
Examples:
```jldoctest
julia> @logscale dΠ "dΠ" Decipies π 10
julia> using Unitful: V, W
julia> @logscale dΠ "dΠ" Decipies π 10 false
julia> @dΠ π*V/1V
Expand All @@ -325,17 +332,16 @@ julia> @dΠ π^2*V/1V
40.0 dΠ (1 V)
julia> @dΠ π*W/1W
10.0 dΠ (1 V)
10.0 dΠ (1 W)
```
"""
macro logscale(symb,abbr,name,base,prefactor)
# name is a symbol
# abbr is a string
li = Symbol("li_", name)

macro logscale(symb,abbr,name,base,prefactor,irp)
quote
Unitful.abbr(::Unitful.LogInfo{$(QuoteNode(name))}) = $abbr

const $(esc(name)) = Unitful.LogInfo{$(QuoteNode(name)), $base, $prefactor}
Unitful.isrootpower(::Type{$(esc(name))}) = $irp

const $(esc(symb)) = Unitful.MixedUnits{Unitful.Gain{$(esc(name))}}()

macro $(esc(symb))(::Union{Real,Symbol})
Expand All @@ -353,8 +359,8 @@ macro logscale(symb,abbr,name,base,prefactor)

function (::$(esc(:typeof))($(esc(symb))))(num::Number, den::Number)
dimension(num) != dimension(den) && throw(DimensionError(num,den))
# dimension(num) == NoDims &&
# throw(ArgumentError("cannot use this macro with dimensionless numbers."))
dimension(num) == NoDims &&
throw(ArgumentError("cannot use with dimensionless numbers."))
return Level{$(esc(name)), den}(num)
end

Expand Down
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1125,8 +1125,8 @@ end

@testset ">> Gain" begin
@test 20dB + 10dB === 30dB
@test 20dB * 20dB === 40dB # support both +*, for sake of generic programming...
@test 20dB - 10dB === 10dB
@test_throws ErrorException 20dB * 20dB
@test_throws ErrorException 1dB + 1Np
end

Expand Down

0 comments on commit c31e249

Please sign in to comment.