Swiftpack.co - Package - ole/Ampere

Ampere

By Ole Begemann, July 2016

A Swift library that extends Foundation’s units and measurements APIs with type-safe multiplication and division.

Requirements

Swift 5.0 or higher.

Ampere should work both on Apple OSes and on other platforms where Swift is available (Linux, Windows).

Dependencies

  • Foundation
  • On Apple platforms, the minimum deployment targets are (I believe):
    • iOS 10
    • macOS 10.12
    • tvOS 10
    • watchOS 3

Usage

The library is a Swift Package Manager package. Add this line to your Package.swift file to add Ampere as a dependency:

let package = Package(
    ...
    dependencies: [
        .package(url: "https://github.com/ole/Ampere.git", from: "0.4.0"),
    ],
    ...

In your code, import Ampere like so:

import Ampere

as an Xcode project that builds an iOS Framework target. I have not (yet) set it up for CocoaPods, Carthage, or the Swift Package Manager. (Although I assume Carthage should build it out of the box.)

Clone the repository, open the project in Xcode, and check out the tests. If you want to use this in your own project in the current form, drag and drop the project file into your Xcode project and add the framework to your linked libraries, or copy the files directly into your project.

Examples

To compute a velocity, we can divide length by time, i.e. Measurement<UnitLength> / Measurement<UnitDuration>:

import Ampere

let length = Measurement(value: 100, unit: UnitLength.meters)
let time = Measurement(value: 10, unit: UnitDuration.seconds)
let speed = length / time
// → 10.0 m/s 🎉

To compute energy, multiply power by time, i.e. Measurement<UnitPower> * Measurement<UnitDuration>:

import Ampere

let power = Measurement(value: 20, unit: UnitPower.kilowatts)
let time = Measurement(value: 3, unit: UnitDuration.hours)
let energy: Measurement<UnitEnergy> = power * time
// → 216000000.0 J
energy.converted(to: .kilowattHours)
// → 60.0 kWh

Notice that in this case we had to explicitly specify the type of the energy variable in let energy: Measurement<UnitEnergy>. Sometimes the compiler cannot infer the correct type automatically and we have to help it.

Additional Units

This library defines the following custom units because they are not included in Foundation:

  • UnitForce, measured in newtons. The base unit is .newtons.

Implemented Relations

This library currently defines the following relations:

Relation Example
area = length × length 1 m² = 1 m × 1 m
volume = area × length 1 m³ = 1 m² × 1 m
density = mass / volume 1 kg/m³ = 1 kg / 1 m³
velocity = length / time 1 m/s = 1 m / 1 s
acceleration = velocity / time 1 m/s² = 1 m/s / 1 s
energy = power × time 1 J = 1 W × 1 s
resistance = voltage / current 1 Ω = 1 V / 1 A
charge = current × time 1 C = 1 A × 1 s
force = mass × acceleration 1 N = 1 kg × 1 m/s²

Adding Relations

Missing something? Pull requests welcome! It’s very easy to add additional relations. All you have to do is to conform your desired type to the UnitProduct protocol, like so:

/// Energy = Power * Duration
/// 1 J = 1 W * 1 s
extension UnitEnergy: UnitProduct {
    public typealias Factor1 = UnitPower
    public typealias Factor2 = UnitDuration
    public typealias Product = UnitEnergy

    public static func defaultUnitMapping() -> (Factor1, Factor2, Product) {
        return (.watts, .seconds, .joules)
    }
}

This says that the conforming type UnitEnergy is the product of the two associated types Factor1 (UnitPower) and Factor2 (UnitDuration).

The defaultUnitMapping method defines the units for the three types that should be used for computations. By returning (.watts, .seconds, .joules), we say that 1 W × 1 s = 1 J. These must be consistent so that the resulting equation is valid. We could just as well have returned (.kilowatts, .hours, .kilowattHours), but something like (.watts, .hours, .joules) would produce wrong results.

Note that Ampere expresses all relations as products, i.e. multiplications. If you want to express a ratio, such as speed = length / time, you first have to rearrange the equation into a multiplication, i.e. length = speed × time. This is why this particular relation is defined on UnitLength and not UnitSpeed. A separate protocol for ratios of units should not be necessary because every ratio can be transformed into an equivalent product equation.

For units that are the square of another units, i.e. where both factors of the product are the same type, use the UnitSquare protocol. An existing example of this is UnitArea, which is UnitLength * UnitLength.

Limitations

Currently some relations cannot be expressed due to conflicts with other definitions. As an example, consider the relation pressure = force / area. In Ampere, we would express this in terms of force = pressure × area because all relations are expressed as multiplications (see above).

However, force = pressure × area conflicts with the existing force = mass × accelerationUnitForce can only conform once to UnitProduct so we cannot express both of these. It could be that the only solution is to introduce a separate UnitRatio protocol after all, but that’s not implemented yet.

More Information

I wrote a series of blog posts about this: Part 1, Part 2, Part 3. Part 2 in particular explains the implementation in a lot of detail.

License

MIT License. See LICENSE.txt file for details.

Github

link
Stars: 168

Dependencies

Used By

Total: 0

Releases

0.4.1 -

Small improvement to the package manifest (see #26, thanks @gbreen12). No code changes.

0.4: Switch to Swift Package Manager -

  • Converted the Xcode project into a SwiftPM package.
  • Tested compatibility with Swift 5.1.
  • Added continuous integration testing for Linux. One test still fails on Linux, but that’s due to a bug in swift-corelibs-foundation (see #25).
  • Switched from Travis CI to GitHub Actions for automated testing.

0.3: Swift 5.0 -

Migrate code to Swift 5.0. No other changes.

0.2: Swift 3.1 -

Added compatibility with Swift 3.1 (see #16).

0.1: Initial release -