The abstract type on which stub methods are defined to allow other packages to overwrite their behavior.

To Do

To Do: Ensure we have these stubs!

This supertype is parametrized by the constituent types T for durations such as the period ("time"), U for fractional periods ("unitless"), and V for values assumed by the dutycycle.


Make no assumptions: An DutyCycle is by definition a rather atypical number in the context of numbers usually encountered. Hence many concepts, such as the ordered nature of Real numbers do not apply: You cannot e.g. expect an operator such as isless (<) to be defined for DutyCycles.


An incoherent DutyCycle does not have phases associated with the values it assumes. Instead, it only has effective (fractional) durations that each of its values is assumed over a cycle period in total, but not necessarily (and not usually) in one go.


The concrete, parametrized type for an AbstractCoherentDutyCycle{T,U,V}. It is parametrized on type T for temporal values (for the total period, can optionally include units), on type U which is the same type without units (for phases) and on type V (for values). Note that T and V must not include units; the way to get units attached to a DutyCycle is to use it as the underlying type of a Unitful.Quantity rather than the other way around.


Construct a CoherentDutyCycle from arguments of the type duration => value. The types involved are determined by the first pair and there must be at least one. Note the warning at the analogous but more general cycle method that can accept units in the values.


Turn a CoherentDutyCycle{T,U,V} into an IncoherentDutyCycle{T,U,V}. Note that a suitable type T for the period (one that can store infinity such as a Rational or AbstractFloat) must be provided that may differ from the T used for the CoherentDutyCycle, as that also permits Integer types for the period that are not supported by IncoherentDutyCycle{T,U,V}.

Note: This is not as useful as the method incoherent! which has the advantage of retaining more information and hence more utility.


Calculate a type-appropriate average (dispatching on the type, specifically the Unitful.Dimension part) which could be a root-mean-square (see rms) or a simple average, also known as mean (see mean).

To implement this for custom types, see autoavgfunc.

Note that certain dimensions (units) can have either a "typical" rms or simple mean average, depending on context. For example, lengths could be seen as calling for an average when used as a dimension, or as rms when used as an oscillation amplitude. Here the convention is followed that electrical currents and voltages have rms averages and all other units have mean averages.

Note that this function is used implicitly for comparisons involving a DutyCycle and a normal number.


Helper function for autoavg. Returns a function that calculates the type appropriate average. Used by Base.show to determine what to print as a summary of the type appropriate averaging method. Exported for ease of amending with custom functions.


Calculate the rational ratio abs(a//b) (or, if a and b are not both numerically exact types, a rationalization of abs(a/b)) of its arguments if these behave coherently, i.e. if they are commensurate and hence their ratio is rational, neither zero or infinity, and has zero uncertainty, see hascoherence_ratio for modeling uncertainties. Otherwise, return an infinite rational which is one with zero denominator (and nonzero nominator, a condition enforced by the implementation of Rational in julia). The return type is big (i.e. Rational{BigInt}) if and only if either one of the arguments is big (e.g. BigFloat) or the call is performed with a result type parameter preprended before the other arguments, i.e. coherenceratio(Rational{BigInt}, a, b). Beware that when using small integer types, the result may overflow, as is the usual behavior of for small integer types in julia.

Keyword arguments can be used to influence the behavior. If maxperiods is positive, then this restricts the maximum number of repetitions of the main arguments that are allowed for them to be considered coherent. If tol is a positive number, it is used as tolerance for the rationalization of ratios of arguments if at least one of them is an inexact type. If it is not given (or nonpositive), it will be automatically assigned based on the numerical precision of the types of the arguments. Note that setting tol below 1.0/maxperiods will preempt maxperiods from taking effect. If tol is given (and, again, positive) it will also be used to access the relative uncertainty in the coherence ratio to be returned, allowing a nonzero uncertainty to still be considered coherent.

To Do: Better than an overflow would be to return an infinite value (incorrectly indicating incoherenence) in that case, as the extreme of taking infinity instead of an overflow would be incoherence (but the limit of taking the overflowing values to infinity could be either zero or infinity).


The coherence time of one or more DutyCycles.


For a single duty cycle, the returned coherence time is derived from its period and the uncertainty of the period based on the assumption that a perfectly repeating dutycycle is compared to an absolutely certain reference timing.

To Do

Consider implementing optional keyword arguments for modeling phase noise.

To Do

Implement for more than one DutyCycle.


Construct a CoherentDutyCycle{T,U,V} from arguments of the type duration => value.


If you specify durations e.g. in seconds, the resulting CoherentDutyCycle will act incoherently with those having the default period: To obtain coherence with them, you have to derive the durations from default_period or change that default. This should not be a problem in practice, however, because the default_period is applied, by default, only to numbers that are constant in time.

To Do

Format the other method documentation (docstrings) in the recommended way: A single, short sentence, then the signature, then an optional paragraph or more of explanation, hints, warnings, etc. See julia's documentation on documentation.


Return the default period for any DutyCycle with no period specified. Corresponds to 50 Hz, the electrical grid frequency in Europe, with some arbitrarily chosen uncertainty to enforce incoherent behavior with DutyCycle objects with a specified period (if that comes from a different source).


This period is used to promote non-dutycycles to DutyCycles: By default all values are assumed to "cycle" through a constant value at 50 Hz.

Design Rationale

Giving all duty cycles a (slightly) uncertain period ensures that coherence and incoherence can be modelled even using values that come with no information regarding their temporal variation. Assuming them to actually be constant in time ensures consistency and allows upgrading code that uses "normal" (constant in time) numbers to use dutycycles.

To change this default behavior, provide a method taking one argument (the value being promoted to a DutyCycle) to return the period to be used in that instance. For example, the following code changes the default to use a period corresponding to a 60 Hz (instead of 50 Hz) frequency:

using DutyCycles, Unitful
using Unitful: Ω, mA, Hz
DutyCycles.default_period(::Number) = 50Hz/60Hz*default_period()
I = dutycycle(0.5, onvalue = 50mA)
U = dutycycle(0.5, onvalue = 50Ω * 50mA)
uconvert(Hz, fundamental_frequency(U * I))

# output

60.0 ± 3.0e-6 Hz

Another example is changing the default treatmeant of phase coherency between automatically promoted numbers of behaving coherently to behaving incoherently:

using DutyCycles, Unitful, Measurements
using Unitful: Ω, mA, mW, Hz
DutyCycles.default_period(::Number) = 1/((60±3e-6)*Hz)
I = dutycycle(0.5, onvalue = 50mA)
U = dutycycle(0.5, onvalue = 50Ω * 50mA)
uconvert(Hz, fundamental_frequency(U * I))

# output

0.0 Hz

The output of a zero frequency (with undefined uncertainty) means that the DutyCycles were treated incoherently during the multiplication U*I.


Return the durations associated with the values[@ref] assumed by a coherent DutyCycle, in the same order that they are returned, such that durations(d)[i] corresponds to values(d)[i] and zip(durations(d), values(d)) makes sense for a DutyCycle d. For an incoherent DutyCycle, return a vector of missings of the appropriate length.


Construct a CoherentDutyCycle{T,U,V} from a given dutycycle value, with optional, named keyword arguments avg, offvalue, onvalue, period and phase.

dutycycle(duty; kwargs...)

If an onvalue is supplied, it overrules any supplied avg (average) value. By default it averages to the floating point value 1.0, with no units, with a zero offvalue.

To Do

Add optional parameters ontime and offtime (and try calculating the period from the sum of these before defaulting to default_period).

It is possible to use this function without specifying a duty cycle (which normally constitutes the first argument). Note that you have to make the lack of it explicit by starting the argument list with a semicolon. Then the keyword avg for the average value is no longer optional and determines the duty cycle together with the optional keyword arguments onvalue and offvalue.


Calculate the extrema (minimal and maximal values) ever assumed by one or more dutycycles and returns them as a pair (minimum, maximum) of non-DutyCycle numbers. Can be used with a non-empty iterator or a collection and will then return the absolute maximum value assumed by any of the items in it.


Return the fractional (per cycle) durations associated with the values[@ref] assumed by a DutyCycle, in the same order that they are returned, such that fractionaldurations(d)[i] corresponds to values(d)[i] and zip(fractionaldurations(d), values(d)) makes sense for a DutyCycle d.


Return the total fractional (per cycle) time associated with the values[@ref] assumed by a DutyCycle, in the same order that they are returned. A total fractional duration is the sum of durations during which a given value occurs. Note that the duty cycle may not assume this value for any given length of time, as an incoherent DutyCycle may (and, in general, will) vary its value faster than this.


Return true if there is a rational ratio with (within numerical precision) zero uncertainty between the periods given as arguments. Keyword arguments are tol, the tolerance for accepting relative uncertainty for this ratio, and maxperiods, the maximum number of repetitions of either period to consider coherent, even if a lower uncertainty in their ratio suggests they should be considered coherent.


This method takes a DutyCycle and uncorrelates its period from all other periods of other DutyCycles. This effectively turns it into an incoherent DutyCycle as it then behaves incoherently with regard to all other DutyCycles except that it retains its type (if it is a CoherentDutyCycle{T,U,V}, it remains one with associated period and phase information although the underlying type for the period may change).


Return true if and only if all arguments (general values, especially dutycycles) are coherent with each other, i.e. have a rational ratio to each other and are correlated such that this ratio has (numerically) zero uncertainty if their type models an uncertainty. Use the Measurement type from packet Measurements (see its github repository) to model uncertainties.

Technically, a coherence relationship can only be defined between at least two quantities. However, to generalize as much as possible, zero or one arguments can be passed and will be treated as coherent if a given argument has no uncertainty and it is not otherwise disqualified (by e.g. being zero, infinite, or another NaN value).


Calculate the maximum value assumed by a DutyCycle (or another number). Can be used with a non-empty iterator or a collection and will then return the absolute minimum value assumed by any of the items in it.


Calculate the mean (time-averaged) value of a DutyCycle.


There is already a mean function in julia's standard library, Statistics.mean. It has different semantics: It is used to calculate an average from a collection or iteration which could make sense to be a DutyCycle itself, so it is one) whilst this mean function calculates the time-average of a single argument. Naturally, both can be combined as Statistics.mean(DutyCycles.mean, x) where x is the collection or iteration of values that can include one or more DutyCycle numbers.


Calculate the maximum value assumed by a DutyCycle (or another number). Can be used with a non-empty iterator or a collection and will then return the absolute maximum value assumed by any of the items in it.


Delay a dutycycle by the given phase in units of fractional cycles. Note that phases can also be supplied in the Unitful units rad or °. To Do: Implement units using package UnitfulAngles.


If no units are given, the phase is assumed to be in turns, i.e. 1 corresponds to a full cycle, not to 1 radians. The rationale is that this way, exact phases can be represented using rational numbers. Do not assume that you can pass a phase in radians as you can do with (sensibly defined) trigonometry functions!

To Do

Implement this method.


The power spectral density of a DutyCycle (or a number promoted to a DutyCycle).

psd(n; kwargs...)

See spectrum for the optional keyword arguments.


Calculate the root-mean-squared value of a DutyCycle or a Number (which is the identity but useful for writing code that does not care whether it is given a DutyCycle or a Number).


The (amplitude) spectrum of a DutyCycle or number.


This returns the spectrum as a function of the ordinary frequency $\nu$ (or u). This is the unitary Fourier transform for ordinary frequencies with appropriate Unitful units:

\[\mathrm{spectrum}\big( f(t) \big)(\nu) = \int_{-\infty}^{+\infty} f(t) \, e^{-2 \pi i t \nu} \, \mathrm{d}t\]
To do

Consider also implementing this for a function (waveform).


Return the value assumed by a DutyCycle at an instant in time or a given phase. If the Unitful dimension of the second argument are ambigious between the two, default to a phase.


Return the values assumed by a DutyCycle during a period.


For a DutyCycle that resulted from an operation involving incoherent DutyCycle objects, there is no guarantee that the values returned will ever occur in that specific order. In fact, they typically will never do so.


Return a function that describes the waveform of a coherent or (with a caveat, see below) an incoherent DutyCycle as a function of fractional duration, optionally stripping units if the optional keyword argument stripunits is set to true.

The argument to this function, time, should share the dimensions of ther period of the dutycycle but can alternatively be dimensionless to indicate a phase (which only works if the period is not dimensionless as well). Unitful quantities can be used to specify units, including angular units for the phase.

The default phase unit for `waveform` is Radians!

Elsewhere, including the function phaseshift, phases are by default in units of cycles. This one differs.

To Do

Change the default elsewhere and point out that by passing values derived from UnitfulAngles.turn (or perhaps a DutyCycles.cycles unit to be made equivalent to it), exact fractions of a cycle can be specified.

This method also accepts incoherent waveforms, but scrambles their phase, returning a (properly weighted) random choice from the values it assumes.

To Do

If/when upgrading IncoherentDutyCycles to support information about the shortest spikes that can occur, upgrade this behavior to reflect this minimum dwell time at a given value.


Internally used type alias to simplify checking for built-in number types which produce exact ratios, i.e. those of type ExactNumber. Use e.g. as (a isa ExactNumber && b isa ExactNumber) ? a//b : a/b.


This is the type of numbers (Real and Complex) that are allowed for values of a DutyCycle. To construct more general numbers, e.g. to attach units using the package Unitful, use a DutyCycle as underlying type for such a more general type rather than using e.g. a Unitful number as a value type of a DutyCycle. Note that this is not only possible but happens automatically when you try to attach units.


The operator * works in the usual manner.


The operator + works in the usual manner.


The operator - works in the usual manner.


The operator / works in the usual manner.


Equality operator. Should return true only if the all values and phases of the DutyCycle object being compared match precisely, without allowing for numerical inaccuracies (but allowing for different internal representations). In normal usage, one prefers isapprox.

Note: The current implementation contains the bug that phases only need to match approximately.


Exponentiation works in the usual manner.


Exponentiation works in the usual manner.


Exponentiation works in the usual manner.


The Base.isapprox method for type DutyCycle, also accessible in julia as the operator . Extra keyword arguments are assumed to relate to the values whilst for phases a sensible default (the square root of the machine precision for a value of 1) is always used. Note that this implies ignoring (ultra)short spikes even if their time-integral is significant. The periods of the DutyCycles must match (within the default tolerance): False is returned even if one DutyCycle is simply a repetition of the other.

Outdated Note: Disregard

A comparison with a regular number (or Unitful.Quantity) will succeed even if only the autoavg of the values agrees. If this behavior is undesired, the keyword argument matchaverage=false must be given. The existing Base.isapprox method and the derived operators and `` are amended accordingly to allow passing this new option even for comparisons where no DutyCycle objects are involved.

To Do

Update the above documentation which is outdated: matchaverage=false is the default now and it would make sense to completely remove this option.


Return true for a dutycycle that ever assumes a value for which isinf is true, and false otherwise.


Return true for a dutycycle if ismissing is true for all values assumed by the DutyCycle, and false otherwise.


Return true for a dutycycle that ever assumes a value for which isnan is true, and false otherwise.


Return true for a dutycycle if isnothing is true for all values assumed by the DutyCycle, and false otherwise.


The operator max works in a way that merits explanation: It returns the maximum instantenous value, that is a new dutycycle which at every point in time assumes the maximum value of either of the arguments.


The operator min works in a way that merits explanation: It returns the minimum instantenous value, that is a new dutycycle which at every point in time assumes the minimum value of either of the arguments.


Method that handles showing (producing) output from a DutyCycle.


Return true if there is at least one element in the collection passed as argument. Prepend a predicate function to only return true if there is at least one element for which it is true.


Return whatever multiplicative element is not a DutyCycle in a number (e.g. units) or the integer 1 if there is no such thing.


Convenience function to assert that all internal relationships in a DutyCycle are correct. It is not exported because it is not expected that a normal user will need to check these, but included to allow such a test if it is needed (e.g. in unit tests or bug reports).