diff --git a/QuantumMechanics/AnyParticle.swift b/QuantumMechanics/AnyParticle.swift index 25bf7f47..0e67a1af 100644 --- a/QuantumMechanics/AnyParticle.swift +++ b/QuantumMechanics/AnyParticle.swift @@ -15,12 +15,10 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// ``ParticleLike`` whose type information has been erased. public struct AnyParticleLike: ParticleLike { public let spin: Spin - public let charge: Measurement + public let charge: ElectricCharge public let symbol: String /// ``ParticleLike`` based on which this one was initialized. @@ -42,9 +40,9 @@ public struct AnyParticleLike: ParticleLike { } } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { base.getMass(approximatedBy: approximator) } + public func getMass(approximatedBy approximator: Approximator) -> Mass { + base.getMass(approximatedBy: approximator) + } } extension AnyParticleLike: Equatable { @@ -56,7 +54,7 @@ extension AnyParticleLike: Equatable { /// ``Particle`` whose type information has been erased. public struct AnyParticle: ParticleLike { public let spin: Spin - public let charge: Measurement + public let charge: ElectricCharge public let symbol: String /// ``Particle`` based on which this one was initialized. @@ -80,9 +78,9 @@ public struct AnyParticle: ParticleLike { } } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { base.getMass(approximatedBy: approximator) } + public func getMass(approximatedBy approximator: Approximator) -> Mass { + base.getMass(approximatedBy: approximator) + } } extension AnyParticle: Equatable { diff --git a/QuantumMechanics/Composite/Hadron.swift b/QuantumMechanics/Composite/Hadron.swift index c08c65f9..552bdbaf 100644 --- a/QuantumMechanics/Composite/Hadron.swift +++ b/QuantumMechanics/Composite/Hadron.swift @@ -15,8 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// ``Particle`` composed by two or more ``Quark``s which are bound by strong force. It is the /// compositor of nucleons — such as protons and neutrons — and, therefore, the most common /// composite ``Particle`` in the universe. @@ -71,8 +69,10 @@ extension Hadron where Self: ParticleLike { } extension Hadron where Self: ColoredParticleLike { - public var charge: Measurement { - quarks.reduce(.zero) { charge, quark in quark.charge + charge } + public var charge: ElectricCharge { + var quantityInBaseUnit = 0.0 + for quark in quarks { quantityInBaseUnit += quark.charge.quantityInBaseUnit } + return .init(quantityInBaseUnit: quantityInBaseUnit) } public var colorLike: ColorLike { white as! ColorLike } } diff --git a/QuantumMechanics/Composite/Pion/NegativePion.swift b/QuantumMechanics/Composite/Pion/NegativePion.swift index 75b7536a..b3e94a2b 100644 --- a/QuantumMechanics/Composite/Pion/NegativePion.swift +++ b/QuantumMechanics/Composite/Pion/NegativePion.swift @@ -15,8 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// ``Pion`` with a negative ``charge`` (π⁻), resulted from d + ū. /// /// - SeeAlso: ``DownQuark`` @@ -27,9 +25,7 @@ public struct NegativePion: Equatable, Pion { fileprivate init(quarks: [AnyQuarkLike]) { self.quarks = quarks } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { + public func getMass(approximatedBy approximator: Approximator) -> Mass { approximator.approximate(chargedPionMass, chargedPionMassStatisticalUncertainty, .zero) } } diff --git a/QuantumMechanics/Composite/Pion/Pion.swift b/QuantumMechanics/Composite/Pion/Pion.swift index bed7a39b..4a25fada 100644 --- a/QuantumMechanics/Composite/Pion/Pion.swift +++ b/QuantumMechanics/Composite/Pion/Pion.swift @@ -15,13 +15,11 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// Base value for calculating an approximation of the mass of a charged ``Pion``. -let chargedPionMass = Measurement(value: 139.57039, unit: UnitMass.gigaelectronvolt) +let chargedPionMass = Mass.gigaelectronvoltsPerLightSpeedSquared(139.57039) /// Statistical uncertainty for calculating an approximation of the mass of a charged ``Pion``. -let chargedPionMassStatisticalUncertainty = Measurement(value: 180, unit: UnitMass.electronvolt) +let chargedPionMassStatisticalUncertainty = Mass.electronvoltsPerLightSpeedSquared(180) /// ``Meson`` composed by ``Quark``-antiquark pairs, produced most commonly via high-energy /// collisions between ``Hadron``s and specific ``Particle``-antiparticle annihilation. diff --git a/QuantumMechanics/Composite/Pion/PositivePion.swift b/QuantumMechanics/Composite/Pion/PositivePion.swift index a98bc676..56d6ef3f 100644 --- a/QuantumMechanics/Composite/Pion/PositivePion.swift +++ b/QuantumMechanics/Composite/Pion/PositivePion.swift @@ -15,8 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// ``Pion`` with a positive ``charge`` (π⁺), resulted from u + d̄. /// /// - SeeAlso: ``UpQuark`` @@ -27,9 +25,7 @@ public struct PositivePion: Equatable, Pion { fileprivate init(quarks: [AnyQuarkLike]) { self.quarks = quarks } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { + public func getMass(approximatedBy approximator: Approximator) -> Mass { approximator.approximate(chargedPionMass, chargedPionMassStatisticalUncertainty, .zero) } } diff --git a/QuantumMechanics/Constants.swift b/QuantumMechanics/Constants.swift index b8f79ca5..c566044c 100644 --- a/QuantumMechanics/Constants.swift +++ b/QuantumMechanics/Constants.swift @@ -15,7 +15,5 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// The speed of light. -let c = Measurement(value: 299_792_458, unit: UnitSpeed.metersPerSecond) +let c = Speed.metersPerSecond(299_792_458) diff --git a/QuantumMechanics/Elementary/AnyQuark.swift b/QuantumMechanics/Elementary/AnyQuark.swift index 677e76d9..9e312c70 100644 --- a/QuantumMechanics/Elementary/AnyQuark.swift +++ b/QuantumMechanics/Elementary/AnyQuark.swift @@ -15,8 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - /// ``QuarkLike`` whose flavor information has been erased. public struct AnyQuarkLike: Discrete, QuarkLike { /// ``Quark`` based on which this one was initialized. @@ -27,7 +25,7 @@ public struct AnyQuarkLike: Discrete, QuarkLike { } public let spin: Spin - public let charge: Measurement + public let charge: ElectricCharge public let symbol: String public let colorLike: AnySingleColorLike @@ -49,9 +47,9 @@ public struct AnyQuarkLike: Discrete, QuarkLike { } } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { base.getMass(approximatedBy: approximator) } + public func getMass(approximatedBy approximator: Approximator) -> Mass { + base.getMass(approximatedBy: approximator) + } } extension AnyQuarkLike: Equatable { @@ -74,7 +72,7 @@ public struct AnyQuark: Discrete, Quark { }.sorted(by: <) public let spin: Spin - public let charge: Measurement + public let charge: ElectricCharge public let symbol: String public let colorLike: AnySingleColor @@ -96,9 +94,9 @@ public struct AnyQuark: Discrete, Quark { } } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { base.getMass(approximatedBy: approximator) } + public func getMass(approximatedBy approximator: Approximator) -> Mass { + base.getMass(approximatedBy: approximator) + } } extension AnyQuark: Equatable { diff --git a/QuantumMechanics/Elementary/BottomQuark.swift b/QuantumMechanics/Elementary/BottomQuark.swift index 5a22783a..a06cf695 100644 --- a/QuantumMechanics/Elementary/BottomQuark.swift +++ b/QuantumMechanics/Elementary/BottomQuark.swift @@ -15,26 +15,20 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Base value for calculating an approximation of the mass of a ``BottomQuark``. -private let baseMass = Measurement(value: 4.18, unit: UnitMass.gigaelectronvolt) - -/// Statistical uncertainty for calculating an approximation of the mass of a ``BottomQuark``. -private let massStatisticalUncertainty = Measurement(value: 0.03, unit: UnitMass.gigaelectronvolt) - /// Second heaviest ``Quark``, with a Lagrangian mass of 4.18 ± 0.03 GeV/*c*². Decays to a /// ``CharmQuark``. public struct BottomQuark: Quark { public let symbol = "b" - public let charge = negativeOneThirdOfE + public let charge = ElectricCharge.elementary(-1 / 3) public let colorLike: ColorLike public init(colorLike: ColorLike) { self.colorLike = colorLike } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { - approximator.approximate(baseMass, massStatisticalUncertainty, .zero) + public func getMass(approximatedBy approximator: Approximator) -> Mass { + approximator.approximate( + .gigaelectronvoltsPerLightSpeedSquared(4.18), + .gigaelectronvoltsPerLightSpeedSquared(0.03), + .zero + ) } } diff --git a/QuantumMechanics/Elementary/CharmQuark.swift b/QuantumMechanics/Elementary/CharmQuark.swift index a0535e4b..a0f74f4f 100644 --- a/QuantumMechanics/Elementary/CharmQuark.swift +++ b/QuantumMechanics/Elementary/CharmQuark.swift @@ -15,26 +15,20 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Base value for calculating an approximation of the mass of a ``CharmQuark``. -private let baseMass = Measurement(value: 1.275, unit: UnitMass.gigaelectronvolt) - -/// Statistical uncertainty for calculating an approximation of the mass of a ``CharmQuark``. -private let massStatisticalUncertainty = Measurement(value: 25, unit: UnitMass.megaelectronvolt) - /// Third heaviest ``Quark``, with a Lagrangian mass of 1.275 ± 0.025 GeV/*c*². Decays to a /// ``StrangeQuark``. public struct CharmQuark: Quark { public let symbol = "c" - public let charge = twoThirdsOfE + public let charge = ElectricCharge.elementary(2 / 3) public let colorLike: ColorLike public init(colorLike: ColorLike) { self.colorLike = colorLike } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { - approximator.approximate(baseMass, massStatisticalUncertainty, .zero) + public func getMass(approximatedBy approximator: Approximator) -> Mass { + approximator.approximate( + .gigaelectronvoltsPerLightSpeedSquared(1.275), + .megaelectronvoltsPerLightSpeedSquared(25), + .zero + ) } } diff --git a/QuantumMechanics/Elementary/DownQuark.swift b/QuantumMechanics/Elementary/DownQuark.swift index be5036ab..6b54663b 100644 --- a/QuantumMechanics/Elementary/DownQuark.swift +++ b/QuantumMechanics/Elementary/DownQuark.swift @@ -15,29 +15,20 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Base value for calculating an approximation of the mass of a ``DownQuark``. -private let baseMass = Measurement(value: 4.8, unit: UnitMass.megaelectronvolt) - -/// Statistical uncertainty for calculating an approximation of the mass of a ``DownQuark``. -private let massStatisticalUncertainty = Measurement(value: 0.5, unit: UnitMass.megaelectronvolt) - -/// Systematic uncertainty for calculating an approxiumation to the mass of a ``DownQuark``. -private let massSystematicUncertainty = Measurement(value: 0.3, unit: UnitMass.megaelectronvolt) - /// Second lightest ``Quark``, with a Lagrangian mass of 4.8 ± 0.5 ± 0.3 MeV/*c*². Decays to an /// ``UpQuark``. public struct DownQuark: Quark { public let symbol = "d" - public let charge = negativeOneThirdOfE + public let charge = ElectricCharge.elementary(-1 / 3) public let colorLike: ColorLike public init(colorLike: ColorLike) { self.colorLike = colorLike } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { - approximator.approximate(baseMass, massStatisticalUncertainty, massSystematicUncertainty) + public func getMass(approximatedBy approximator: Approximator) -> Mass { + approximator.approximate( + .megaelectronvoltsPerLightSpeedSquared(4.8), + .megaelectronvoltsPerLightSpeedSquared(0.5), + .megaelectronvoltsPerLightSpeedSquared(0.3) + ) } } diff --git a/QuantumMechanics/Elementary/Quark.swift b/QuantumMechanics/Elementary/Quark.swift index 4e701be1..18158371 100644 --- a/QuantumMechanics/Elementary/Quark.swift +++ b/QuantumMechanics/Elementary/Quark.swift @@ -15,21 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Charge of up-type quarks. -let twoThirdsOfE = Measurement(value: 2 / 3, unit: UnitElectricCharge.elementary) - -/// Charge of down-type quarks. -let negativeOneThirdOfE = Measurement(value: -1 / 3, unit: UnitElectricCharge.elementary) - -/// Base protocol to which ``Quark``s and antiquarks conform. -public protocol QuarkLike: ColoredParticleLike where ColorLike: SingleColorLike {} - -extension QuarkLike where Self: ParticleLike { public var spin: Spin { .half } } - -extension Anti: QuarkLike where Counterpart: Quark {} - /// A quark (q) is an elementary fermion ``ColoredParticle`` which is confined, bound to at least /// another one by gluon ``Particle``s via strong force. It is the only ``Particle`` in the Standard /// Model which experiences each of the four fundamental forces: strong, weak, electromagnetic and @@ -73,3 +58,10 @@ extension Anti: QuarkLike where Counterpart: Quark {} /// - SeeAlso: ``ParticleLike/charge`` /// - SeeAlso: ``Spin/half`` public protocol Quark: ColoredParticle, QuarkLike where ColorLike: SingleColor {} + +extension Anti: QuarkLike where Counterpart: Quark {} + +/// Base protocol to which ``Quark``s and antiquarks conform. +public protocol QuarkLike: ColoredParticleLike where ColorLike: SingleColorLike {} + +extension QuarkLike where Self: ParticleLike { public var spin: Spin { .half } } diff --git a/QuantumMechanics/Elementary/StrangeQuark.swift b/QuantumMechanics/Elementary/StrangeQuark.swift index 81fdfb28..f7cdf765 100644 --- a/QuantumMechanics/Elementary/StrangeQuark.swift +++ b/QuantumMechanics/Elementary/StrangeQuark.swift @@ -15,25 +15,19 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Base value for calculating an approximation of the mass of a ``StrangeQuark``. -private let baseMass = Measurement(value: 95, unit: UnitMass.megaelectronvolt) - -/// Statistical uncertainty for calculating an approximation of the mass of a ``StrangeQuark``. -private let massStatisticalUncertainty = Measurement(value: 5, unit: UnitMass.gigaelectronvolt) - /// Third lightest ``Quark``, with a Lagrangian mass of 95 ± 5 MeV/*c*². Decays to a ``DownQuark``. public struct StrangeQuark: Quark { public let symbol = "s" - public let charge = negativeOneThirdOfE + public let charge = ElectricCharge.elementary(-1 / 3) public let colorLike: ColorLike public init(colorLike: ColorLike) { self.colorLike = colorLike } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { - approximator.approximate(baseMass, massStatisticalUncertainty, .zero) + public func getMass(approximatedBy approximator: Approximator) -> Mass { + approximator.approximate( + .megaelectronvoltsPerLightSpeedSquared(95), + .gigaelectronvoltsPerLightSpeedSquared(5), + .zero + ) } } diff --git a/QuantumMechanics/Elementary/TopQuark.swift b/QuantumMechanics/Elementary/TopQuark.swift index 1497151f..ee778269 100644 --- a/QuantumMechanics/Elementary/TopQuark.swift +++ b/QuantumMechanics/Elementary/TopQuark.swift @@ -15,36 +15,20 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Base value for calculating an approximation of the mass of a ``TopQuark``. -private let topBaseMass = Measurement(value: 173.21, unit: UnitMass.gigaelectronvolt) - -/// Statistical uncertainty for calculating an approximation of the mass of a ``TopQuark``. -private let topMassStatisticalUncertainty = Measurement( - value: 0.51, - unit: UnitMass.gigaelectronvolt -) - -/// Systematic uncertainty for calculating an approxiumation to the mass of a ``TopQuark``. -private let topMassSystematicUncertainty = Measurement(value: 0.7, unit: UnitMass.gigaelectronvolt) - /// Heaviest ``Quark``, with a Lagrangian mass of 173.21 ± 0.51 ± 0.7 GeV/*c*². Decays to a /// ``BottomQuark``. struct TopQuark: Quark { public let symbol = "t" - public let charge = twoThirdsOfE + public let charge = ElectricCharge.elementary(2 / 3) public let colorLike: ColorLike public init(colorLike: ColorLike) { self.colorLike = colorLike } - func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { + func getMass(approximatedBy approximator: Approximator) -> Mass { approximator.approximate( - topBaseMass, - topMassStatisticalUncertainty, - topMassSystematicUncertainty + .gigaelectronvoltsPerLightSpeedSquared(173.21), + .gigaelectronvoltsPerLightSpeedSquared(0.51), + .gigaelectronvoltsPerLightSpeedSquared(0.7) ) } } diff --git a/QuantumMechanics/Elementary/UpQuark.swift b/QuantumMechanics/Elementary/UpQuark.swift index f23fc38f..d7e6a7d0 100644 --- a/QuantumMechanics/Elementary/UpQuark.swift +++ b/QuantumMechanics/Elementary/UpQuark.swift @@ -15,29 +15,20 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation - -/// Base value for calculating an approximation of the mass of a ``UpQuark``. -private let upBaseMass = Measurement(value: 2.3, unit: UnitMass.megaelectronvolt) - -/// Statistical uncertainty for calculating an approximation of the mass of a ``UpQuark``. -private let upMassStatisticalUncertainty = Measurement(value: 0.7, unit: UnitMass.megaelectronvolt) - -/// Systematic uncertainty for calculating an approxiumation to the mass of a ``UpQuark``. -private let upMassSystematicUncertainty = Measurement(value: 0.5, unit: UnitMass.megaelectronvolt) - /// Lightest ``Quark``, with a Lagrangian mass of 2.3 ± 0.7 ± 0.5 MeV/*c*². As per the Standard /// Model, cannot decay. public struct UpQuark: Quark { public let symbol = "u" - public let charge = twoThirdsOfE + public let charge = ElectricCharge.elementary(2 / 3) public let colorLike: ColorLike public init(colorLike: ColorLike) { self.colorLike = colorLike } - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { - approximator.approximate(upBaseMass, upMassStatisticalUncertainty, upMassSystematicUncertainty) + public func getMass(approximatedBy approximator: Approximator) -> Mass { + approximator.approximate( + .megaelectronvoltsPerLightSpeedSquared(2.3), + .megaelectronvoltsPerLightSpeedSquared(0.7), + .megaelectronvoltsPerLightSpeedSquared(0.5) + ) } } diff --git a/QuantumMechanics/Measurement+Zero.swift b/QuantumMechanics/Measurement+Zero.swift deleted file mode 100644 index 8e35d363..00000000 --- a/QuantumMechanics/Measurement+Zero.swift +++ /dev/null @@ -1,33 +0,0 @@ -// ===-------------------------------------------------------------------------------------------=== -// Copyright © 2025 Supernova. All rights reserved. -// -// This file is part of the Deus open-source project. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the -// GNU General Public License as published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License along with this program. If -// not, see https://www.gnu.org/licenses. -// ===-------------------------------------------------------------------------------------------=== - -import Foundation - -extension Measurement where UnitType == UnitAngle { - /// An angle of 0º. - public static let zero = Measurement(value: 0, unit: UnitType.baseUnit()) -} - -extension Measurement where UnitType == UnitElectricCharge { - /// An electric charge of 0 C. - public static let zero = Measurement(value: 0, unit: UnitType.baseUnit()) -} - -extension Measurement where UnitType == UnitMass { - /// A mass of 0 kg. - public static let zero = Measurement(value: 0, unit: UnitType.baseUnit()) -} diff --git a/QuantumMechanics/Measurement.swift b/QuantumMechanics/Measurement.swift new file mode 100644 index 00000000..7150e9fa --- /dev/null +++ b/QuantumMechanics/Measurement.swift @@ -0,0 +1,296 @@ +// ===-------------------------------------------------------------------------------------------=== +// Copyright © 2025 Supernova. All rights reserved. +// +// This file is part of the Deus open-source project. +// +// This program is free software: you can redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with this program. If +// not, see https://www.gnu.org/licenses. +// ===-------------------------------------------------------------------------------------------=== + +import Foundation + +/// Curvature of an arc between two vectors sharing an origin or an endpoint. +public struct Angle: Measurement { + public let quantityInCurrentUnit: Double + public let conversionCoefficient: Double + public let symbol: String + + public static let baseUnitSymbol = "rad" + + /// Ratio of the length of an arc to its radius. 1 rad = (π / 180)º. + static let radians = Measurable(conversionCoefficient: 1, symbol: Self.baseUnitSymbol) + + public init(quantityInBaseUnit: Double) { + self = .init( + quantityInCurrentUnit: quantityInBaseUnit, + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + } + + private init(quantityInCurrentUnit: Double, conversionCoefficient: Double, symbol: String) { + self.quantityInCurrentUnit = quantityInCurrentUnit + self.conversionCoefficient = conversionCoefficient + self.symbol = symbol + } +} + +/// Classically, electric charge can be explained as the Lorentz force experienced on a particle in +/// the electromagnetic field. Fundamentally, it is the quantity which is conserved upon global +/// phase rotations (U(1)) of fields. +public struct ElectricCharge: Measurement { + public let quantityInCurrentUnit: Double + public let conversionCoefficient: Double + public let symbol: String + + public static let baseUnitSymbol = "e" + + /// Quantity of charge in elementary charge (*e*). *e* is a fundamental constant as per the SI, + /// equivalent to 1.602176634𝑒⁻¹⁹ coulombs and equating to the least amount of electric charge + /// which can exist unconfined in the Universe. + public static let elementary = Measurable( + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + + public init(quantityInBaseUnit: Double) { + self = .init( + quantityInCurrentUnit: quantityInBaseUnit, + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + } + + private init(quantityInCurrentUnit: Double, conversionCoefficient: Double, symbol: String) { + self.quantityInCurrentUnit = quantityInCurrentUnit + self.conversionCoefficient = conversionCoefficient + self.symbol = symbol + } +} + +/// Accumulation of matter in an object, indicative of its resistance to motion. +public struct Mass: Measurement { + public let quantityInCurrentUnit: Double + public let conversionCoefficient: Double + public let symbol: String + + public static let baseUnitSymbol = "eV/c²" + + /// One-millionth of an eV/c². + /// + /// - SeeAlso: ``electronvoltsPerLightSpeedSquared`` + public static let gigaelectronvoltsPerLightSpeedSquared = Measurable( + conversionCoefficient: 1e6, + symbol: "GeV/c²" + ) + + /// One-hundredth of an eV/c². + /// + /// - SeeAlso: ``electronvoltsPerLightSpeedSquared`` + public static let megaelectronvoltsPerLightSpeedSquared = Measurable( + conversionCoefficient: 1e12, + symbol: "MeV/c²" + ) + + /// Amount of work for accelerating an electron through an electric potential difference of 1 volt + /// in vacuum per the square of the speed of light. Its value in joules is the same as that of the + /// constant *e* (standing for the elementary charge): equals to 1.602176634𝑒⁻¹⁹ joules. + /// + /// - SeeAlso: ``ElectricCharge/elementary`` + public static let electronvoltsPerLightSpeedSquared = Measurable( + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + + public init(quantityInBaseUnit: Double) { + self = .init( + quantityInCurrentUnit: quantityInBaseUnit, + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + } + + private init(quantityInCurrentUnit: Double, conversionCoefficient: Double, symbol: String) { + self.quantityInCurrentUnit = quantityInCurrentUnit + self.conversionCoefficient = conversionCoefficient + self.symbol = symbol + } +} + +/// Length–time rate of change in motion of matter or light. +public struct Speed: Measurement { + public let quantityInCurrentUnit: Double + public let conversionCoefficient: Double + public let symbol: String + + public static let baseUnitSymbol = "m/s" + + /// Amount of meters traversed by matter or light at each second. + public static let metersPerSecond = Measurable( + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + + public init(quantityInBaseUnit: Double) { + self = .init( + quantityInCurrentUnit: quantityInBaseUnit, + conversionCoefficient: 1, + symbol: Self.baseUnitSymbol + ) + } + + private init(quantityInCurrentUnit: Double, conversionCoefficient: Double, symbol: String) { + self.quantityInCurrentUnit = quantityInCurrentUnit + self.conversionCoefficient = conversionCoefficient + self.symbol = symbol + } +} + +/// Quantity attached to a unit. +/// +/// A measurement is any given numeric value which is contextualized by a unit, with such unit +/// normally being one of the defined by the +/// [International System of Units](https://www.bipm.org/en/measurement-units) (SI). Because a +/// measurement may be expressed in various units, the base unit is chosen as that in which the +/// quantity is represented by the object internally, from which conversions of the measurement from +/// one unit into another may be performed. +public protocol Measurement: AdditiveArithmetic, Comparable, CustomStringConvertible, + UnitConvertible +{ + /// Factor by which the value in the base unit is multiplied, resulting in the value in the + /// current unit. + /// + /// Formally, given this value as *x*, + /// + /// - ``quantityInCurrentUnit`` = ``quantityInBaseUnit`` / *x*; and + /// - ``quantityInBaseUnit`` = ``quantityInCurrentUnit`` × *x*. + var conversionCoefficient: Double { get } + + /// Quantity in the current unit. + var quantityInCurrentUnit: Double { get } + + /// ``UnitConvertible/symbol`` of the base unit of this ``Measurement``. + static var baseUnitSymbol: String { get } + + /// Initializes this type of ``Measurement`` from a quantity in its base unit. + /// + /// ## Contract + /// + /// Because implementing this initializer is a requirement of the protocol, as of Swift 6.2.1, the + /// compiler cannot enforce consistency. Therefore, by definition every ``Measurement`` + /// initialized by this initializer is expected to have its + /// + /// 1. ``quantityInCurrentUnit`` = ``quantityInBaseUnit``; + /// 2. ``UnitConvertible/conversionCoefficient`` = `1`; and + /// 3. `symbol` = ``baseUnitSymbol``. + /// + /// - Parameter quantityInBaseUnit: Quantity in the base unit. + init(quantityInBaseUnit: Double) +} + +extension Measurement { + /// Reference to the factory of this type of ``Measurement`` in a given unit. + public typealias Unit = KeyPath> + + /// Quantity in the base unit. + public var quantityInBaseUnit: Double { quantityInCurrentUnit * conversionCoefficient } + + /// Produces another ``Measurement``, with the sign of its quantities negated. + /// + /// - Parameter operand: The ``Measurement`` to be negated. + public static prefix func - (operand: Self) -> Self { + return .init(quantityInBaseUnit: -operand.quantityInBaseUnit) + } + + /// Converts this ``Measurement`` into one in another unit. + /// + /// - Parameter unit: Unit into which this ``Measurement`` will be converted. + public func converted(into unit: Unit) -> Self { + .init(quantityInBaseUnit: quantityInBaseUnit * Self.self[keyPath: unit].conversionCoefficient) + } + + /// Formats one of the quantities of this type of ``Measurement``. + /// + /// - Parameter quantity: The quantity (e.g., ``quantityInCurrentUnit`` or ``quantityInBaseUnit``) + /// to be formatted. + static func formatted(quantity: Double) -> String { + FloatingPointFormatStyle(locale: .autoupdatingCurrent).precision(.fractionLength(0...2)) + .grouping(.automatic).format(quantity) + } + + /// Obtains the factory which initializes this type of ``Measurement`` in the given unit. + /// + /// - Parameter unitRepresentable: Representation of the unit. + static func `in`(_ unit: Unit) -> Measurable { Self.self[keyPath: unit] } +} + +extension Measurement where Self: AdditiveArithmetic { + public static var zero: Self { .init(quantityInBaseUnit: 0) } + + public static func - (lhs: Self, rhs: Self) -> Self { + return .init(quantityInBaseUnit: lhs.quantityInBaseUnit - rhs.quantityInBaseUnit) + } + + public static func + (lhs: Self, rhs: Self) -> Self { + return .init(quantityInBaseUnit: lhs.quantityInBaseUnit + rhs.quantityInBaseUnit) + } +} + +extension Measurement where Self: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.quantityInBaseUnit == rhs.quantityInBaseUnit + } +} + +extension Measurement where Self: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs.quantityInBaseUnit < rhs.quantityInBaseUnit + } +} + +extension Measurement where Self: CustomStringConvertible { + /// Describes the unitized value of this ``Measurement`` (e.g., "2 cm"). + public var description: String { + return "\(Self.formatted(quantity: quantityInCurrentUnit)) \(symbol)" + } +} + +/// Initial representation of a measurement, by which the only information provided is the +/// coefficient for converting one ``Measurement`` of the specified type into one of the same type +/// in another unit. Allows for initializing a ``Measurement`` of such type directly by being called +/// as a function with the quantity in the current unit; and, therefore, defining static units. +public struct Measurable: UnitConvertible { + public let conversionCoefficient: Double + public let symbol: String + + fileprivate init(conversionCoefficient: Double, symbol: String) { + self.conversionCoefficient = conversionCoefficient + self.symbol = symbol + } + + /// Initializes a ``Measurement`` of the specified type. + /// + /// - Parameter quantityInCurrentUnit: Quantity in the current unit. + public func callAsFunction(_ quantityInCurrentUnit: Double) -> MeasurementType { + .init(quantityInBaseUnit: quantityInCurrentUnit * conversionCoefficient) + } +} + +/// Specifier of a coefficient for converting a value in the base unit into another in the current +/// one. +public protocol UnitConvertible: Hashable, Sendable { + /// Factor by which the value in the base unit is multiplied, resulting in the value in the + /// current unit. + var conversionCoefficient: Double { get } + + /// Abbreviated textual representation of the unit as per the SI. + var symbol: String { get } +} diff --git a/QuantumMechanics/Particle.swift b/QuantumMechanics/Particle.swift index e290aaec..7d5b723c 100644 --- a/QuantumMechanics/Particle.swift +++ b/QuantumMechanics/Particle.swift @@ -15,7 +15,33 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation +/// Subatomic excitation of an underlying field, which exhibits discrete interactions with other +/// fields. Differs from vacuum fluctuations or delocalized modes on such field in that it is +/// localized: it has a wavepacket of location. Such localization is made possible by +/// collapse-like, interpretation-defined consequence of measuring its wavefunction Ψ(*x*, *t*), +/// where *x* is the position and *t* is the time. +/// +/// ## Classification +/// +/// Particles are divided into two families: +/// +/// Family | Spin | Characterization | +/// --------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +/// Boson | s ∈ ℤ × *ħ* | **Bose–Einstein distribution**: there can be an integer multiple of *nQ* number of particles in a concentration per unit of its volume. | +/// Fermion | s ∈ (ℤ + ½) × *ħ* | **Pauli exclusion**: identical particles of such family cannot have equal quantum states in the same spacetime. | +/// +/// - SeeAlso: ``Spin`` +public protocol Particle: ParticleLike, Opposable {} + +extension Anti: Comparable, ParticleLike where Counterpart: Particle { + public var spin: Spin { counterpart.spin } + public var symbol: String { counterpart.symbol + "̅" } + public var charge: ElectricCharge { -counterpart.charge } + + public func getMass(approximatedBy approximator: Approximator) -> Mass { + counterpart.getMass(approximatedBy: approximator) + } +} /// Base protocol to which ``Particle``s and antiparticles conform. public protocol ParticleLike: Comparable, Sendable { @@ -23,7 +49,7 @@ public protocol ParticleLike: Comparable, Sendable { var spin: Spin { get } /// Force experienced by this type of ``ParticleLike``s in an electromagnetic field. - var charge: Measurement { get } + var charge: ElectricCharge { get } /// Character which identifies this type of ``ParticleLike`` as per the International System of /// Units (SI). @@ -59,9 +85,7 @@ public protocol ParticleLike: Comparable, Sendable { /// Obtains the amount of rest energy of this ``ParticleLike``. /// /// - Parameter approximator: ``Approximator`` of the mass. - func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement + func getMass(approximatedBy approximator: Approximator) -> Mass } extension ParticleLike { @@ -99,33 +123,3 @@ extension ParticleLike where Self: Comparable { extension ParticleLike where Self: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { lhs.isPartiallyEqual(to: rhs) } } - -extension Anti: Comparable, ParticleLike where Counterpart: Particle { - public var spin: Spin { counterpart.spin } - public var symbol: String { counterpart.symbol + "̅" } - public var charge: Measurement { - Measurement(value: -counterpart.charge.value, unit: .elementary) - } - - public func getMass( - approximatedBy approximator: Approximator> - ) -> Measurement { counterpart.getMass(approximatedBy: approximator) } -} - -/// Subatomic excitation of an underlying field, which exhibits discrete interactions with other -/// fields. Differs from vacuum fluctuations or delocalized modes on such field in that it is -/// localized: it has a wavepacket of location. Such localization is made possible by -/// collapse-like, interpretation-defined consequence of measuring its wavefunction Ψ(*x*, *t*), -/// where *x* is the position and *t* is the time. -/// -/// ## Classification -/// -/// Particles are divided into two families: -/// -/// Family | Spin | Characterization | -/// --------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -/// Boson | s ∈ ℤ × *ħ* | **Bose–Einstein distribution**: there can be an integer multiple of *nQ* number of particles in a concentration per unit of its volume. | -/// Fermion | s ∈ (ℤ + ½) × *ħ* | **Pauli exclusion**: identical particles of such family cannot have equal quantum states in the same spacetime. | -/// -/// - SeeAlso: ``Spin`` -public protocol Particle: ParticleLike, Opposable {} diff --git a/QuantumMechanics/Symmetry.swift b/QuantumMechanics/Symmetry.swift index 26e72156..88909359 100644 --- a/QuantumMechanics/Symmetry.swift +++ b/QuantumMechanics/Symmetry.swift @@ -15,7 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation import Geometry internal import Numerics @@ -29,7 +28,5 @@ extension Complex { /// U(1) = exp(*i* × θ) /// /// - Parameter theta: Angle of the rotation. - func u1(by theta: Measurement) -> Self { - self * .exp(.i * theta.converted(to: .radians).value) - } + func u1(by theta: Angle) -> Self { self * .exp(.i * theta.quantityInBaseUnit) } } diff --git a/QuantumMechanics/UnitElectricCharge+Elementary.swift b/QuantumMechanics/UnitElectricCharge+Elementary.swift deleted file mode 100644 index bf0dad51..00000000 --- a/QuantumMechanics/UnitElectricCharge+Elementary.swift +++ /dev/null @@ -1,28 +0,0 @@ -// ===-------------------------------------------------------------------------------------------=== -// Copyright © 2025 Supernova. All rights reserved. -// -// This file is part of the Deus open-source project. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the -// GNU General Public License as published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License along with this program. If -// not, see https://www.gnu.org/licenses. -// ===-------------------------------------------------------------------------------------------=== - -import Foundation - -extension UnitElectricCharge { - /// Amount in elementary charge (*e*). *e* is a fundamental constant as per the SI, equating - /// 1.602176634 × 10⁻¹⁹ C and representing the least amount of electric charge which can exist - /// unconfined in the universe. - static let elementary = UnitElectricCharge( - symbol: "e", - converter: UnitConverterLinear(coefficient: 1.602_176_634 * pow(10, -19)) - ) -} diff --git a/QuantumMechanics/UnitMass+Electronvolt.swift b/QuantumMechanics/UnitMass+Electronvolt.swift deleted file mode 100644 index 31e1aed8..00000000 --- a/QuantumMechanics/UnitMass+Electronvolt.swift +++ /dev/null @@ -1,38 +0,0 @@ -// ===-------------------------------------------------------------------------------------------=== -// Copyright © 2025 Supernova. All rights reserved. -// -// This file is part of the Deus open-source project. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the -// GNU General Public License as published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License along with this program. If -// not, see https://www.gnu.org/licenses. -// ===-------------------------------------------------------------------------------------------=== - -import Foundation - -extension UnitMass { - /// Amount in gigaelectronvolts (GeV/*c*²). - static let gigaelectronvolt = UnitMass( - symbol: "GeV", - converter: UnitConverterLinear(coefficient: 5.6095886 * pow(10, 26) / pow(c.value, 2)) - ) - - /// Amount in megaelectronvolts (MeV/*c*²). - static let megaelectronvolt = UnitMass( - symbol: "MeV", - converter: UnitConverterLinear(coefficient: 5.6095886 * pow(10, 29) / pow(c.value, 2)) - ) - - /// Amount in electronvolts (eV/*c*²). - static let electronvolt = UnitMass( - symbol: "eV", - converter: UnitConverterLinear(coefficient: 5.6095886 * pow(10, 35) / pow(c.value, 2)) - ) -} diff --git a/QuantumMechanicsTests/Anti+ParticleTests.swift b/QuantumMechanicsTests/Anti+ParticleTests.swift index 68f4f6f6..ecb05c71 100644 --- a/QuantumMechanicsTests/Anti+ParticleTests.swift +++ b/QuantumMechanicsTests/Anti+ParticleTests.swift @@ -15,16 +15,13 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation import Testing @testable import QuantumMechanics struct AntiparticleTests { @Test - func chargeIsOpposite() { - #expect(Anti(UpQuark(colorLike: red)).charge == Measurement(value: -2 / 3, unit: .elementary)) - } + func chargeIsOpposite() { #expect(Anti(UpQuark(colorLike: red)).charge == .elementary(-2 / 3)) } @Test func massIsSameAsOfCounterpart() { diff --git a/QuantumMechanicsTests/Composite/Pion/NegativePionTests.swift b/QuantumMechanicsTests/Composite/Pion/NegativePionTests.swift index cb4f555e..971de325 100644 --- a/QuantumMechanicsTests/Composite/Pion/NegativePionTests.swift +++ b/QuantumMechanicsTests/Composite/Pion/NegativePionTests.swift @@ -15,7 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation import Testing @testable import QuantumMechanics @@ -31,15 +30,13 @@ struct NegativePionTests { } @Test - mutating func chargeIsNegativeOneE() { - #expect(negativePion.charge == Measurement(value: -1, unit: .elementary)) - } + mutating func chargeIsNegativeOneE() { #expect(negativePion.charge == .elementary(-1)) } @Test mutating func massIsOneHundredAndThirtyNinePointFiftySevenThousandAndThirtyNineGeV() { #expect( negativePion.getMass(approximatedBy: .base) - == Measurement(value: 139.57039, unit: .gigaelectronvolt) + == .gigaelectronvoltsPerLightSpeedSquared(139.57039) ) } } diff --git a/QuantumMechanicsTests/Composite/Pion/PositivePionTests.swift b/QuantumMechanicsTests/Composite/Pion/PositivePionTests.swift index 2ef82dea..dab08959 100644 --- a/QuantumMechanicsTests/Composite/Pion/PositivePionTests.swift +++ b/QuantumMechanicsTests/Composite/Pion/PositivePionTests.swift @@ -15,7 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation import Testing @testable import QuantumMechanics @@ -31,15 +30,13 @@ struct PositivePionTests { } @Test - mutating func chargeIsOneE() { - #expect(positivePion.charge == Measurement(value: 1, unit: .elementary)) - } + mutating func chargeIsOneE() { #expect(positivePion.charge == .elementary(1)) } @Test mutating func massIsOneHundredAndThirtyNinePointFiftySevenThousandAndThirtyNineGeV() { #expect( positivePion.getMass(approximatedBy: .base) - == Measurement(value: 139.57039, unit: .gigaelectronvolt) + == .gigaelectronvoltsPerLightSpeedSquared(139.57039) ) } } diff --git a/QuantumMechanicsTests/Elementary/QuarkLikeTests.swift b/QuantumMechanicsTests/Elementary/QuarkLikeTests.swift index 43e0461a..32d940ac 100644 --- a/QuantumMechanicsTests/Elementary/QuarkLikeTests.swift +++ b/QuantumMechanicsTests/Elementary/QuarkLikeTests.swift @@ -27,7 +27,7 @@ struct QuarkLikeTests { }) ) func chargeOfUpTypeQuarkIsTwoThirdsOfE(_ quarkLike: AnyQuarkLike) { - #expect(quarkLike.charge == twoThirdsOfE) + #expect(quarkLike.charge == .elementary(2 / 3)) } @Test( @@ -35,7 +35,7 @@ struct QuarkLikeTests { }) ) func chargeOfDownTypeQuarkIsNegativeOneThirdOfE(_ quarkLike: AnyQuarkLike) { - #expect(quarkLike.charge == negativeOneThirdOfE) + #expect(quarkLike.charge == .elementary(-1 / 3)) } } } diff --git a/QuantumMechanicsTests/Elementary/QuarkTests.swift b/QuantumMechanicsTests/Elementary/QuarkTests.swift index a0e1d807..e62f479c 100644 --- a/QuantumMechanicsTests/Elementary/QuarkTests.swift +++ b/QuantumMechanicsTests/Elementary/QuarkTests.swift @@ -15,7 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation import Testing @testable import QuantumMechanics @@ -23,10 +22,7 @@ import Testing struct UpQuarkTests { @Test(arguments: AnySingleColor.discretion.map({ color in UpQuark(colorLike: color) })) func baseMassIsTwoPointThreeMev(_ quark: UpQuark) { - #expect( - quark.getMass(approximatedBy: .base) - == Measurement(value: 2.3, unit: UnitMass.megaelectronvolt) - ) + #expect(quark.getMass(approximatedBy: .base) == .megaelectronvoltsPerLightSpeedSquared(2.3)) } @Test(arguments: AnySingleColor.discretion.map({ color in UpQuark(colorLike: color) })) @@ -36,10 +32,7 @@ struct UpQuarkTests { struct DownQuarkTests { @Test(arguments: AnySingleColor.discretion.map({ color in DownQuark(colorLike: color) })) func baseMassIsFourPointEightMev(_ quark: DownQuark) { - #expect( - quark.getMass(approximatedBy: .base) - == Measurement(value: 4.8, unit: UnitMass.megaelectronvolt) - ) + #expect(quark.getMass(approximatedBy: .base) == .megaelectronvoltsPerLightSpeedSquared(4.8)) } @Test(arguments: AnySingleColor.discretion.map({ color in DownQuark(colorLike: color) })) @@ -49,10 +42,7 @@ struct DownQuarkTests { struct StrangeQuarkTests { @Test(arguments: AnySingleColor.discretion.map({ color in StrangeQuark(colorLike: color) })) func baseMassIsNinetyFivePointZeroMev(_ quark: StrangeQuark) { - #expect( - quark.getMass(approximatedBy: .base) - == Measurement(value: 95.0, unit: UnitMass.megaelectronvolt) - ) + #expect(quark.getMass(approximatedBy: .base) == .megaelectronvoltsPerLightSpeedSquared(95.0)) } @Test(arguments: AnySingleColor.discretion.map({ color in StrangeQuark(colorLike: color) })) @@ -62,10 +52,7 @@ struct StrangeQuarkTests { struct CharmQuarkTests { @Test(arguments: AnySingleColor.discretion.map({ color in CharmQuark(colorLike: color) })) func baseMassIsOnePointTwoSevenFiveGev(_ quark: CharmQuark) { - #expect( - quark.getMass(approximatedBy: .base) - == Measurement(value: 1.275, unit: UnitMass.gigaelectronvolt) - ) + #expect(quark.getMass(approximatedBy: .base) == .gigaelectronvoltsPerLightSpeedSquared(1.275)) } @Test(arguments: AnySingleColor.discretion.map({ color in CharmQuark(colorLike: color) })) @@ -75,10 +62,7 @@ struct CharmQuarkTests { struct BottomQuarkTests { @Test(arguments: AnySingleColor.discretion.map({ color in BottomQuark(colorLike: color) })) func baseMassIsFourPointOneEightGev(_ quark: BottomQuark) { - #expect( - quark.getMass(approximatedBy: .base) - == Measurement(value: 4.18, unit: UnitMass.gigaelectronvolt) - ) + #expect(quark.getMass(approximatedBy: .base) == .gigaelectronvoltsPerLightSpeedSquared(4.18)) } @Test(arguments: AnySingleColor.discretion.map({ color in BottomQuark(colorLike: color) })) @@ -88,10 +72,7 @@ struct BottomQuarkTests { struct TopQuarkTests { @Test(arguments: AnySingleColor.discretion.map({ color in TopQuark(colorLike: color) })) func baseMassIsOneHundredAndSeventyThreePointTwoOneGev(_ quark: TopQuark) { - #expect( - quark.getMass(approximatedBy: .base) - == Measurement(value: 173.21, unit: UnitMass.gigaelectronvolt) - ) + #expect(quark.getMass(approximatedBy: .base) == .gigaelectronvoltsPerLightSpeedSquared(173.21)) } @Test(arguments: AnySingleColor.discretion.map({ color in TopQuark(colorLike: color) })) diff --git a/QuantumMechanicsTests/Elementary/QuarkTests.swift.gyb b/QuantumMechanicsTests/Elementary/QuarkTests.swift.gyb index 0a6c5441..d2a34a8a 100644 --- a/QuantumMechanicsTests/Elementary/QuarkTests.swift.gyb +++ b/QuantumMechanicsTests/Elementary/QuarkTests.swift.gyb @@ -30,7 +30,6 @@ }% ${os.environ.get('LICENSE_HEADER_SWIFT')} -import Foundation import Testing @testable import QuantumMechanics @@ -43,12 +42,12 @@ import Testing % massValue = float(mass[0]) % massUnit = mass[1] % spelledUnitizedMass = ''.join(char for char in inflect.engine().number_to_words(massValue).title() if char.isalpha()) + massUnit.capitalize() - % massUnitReference = "UnitMass.megaelectronvolt" if massUnit == 'MeV' else "UnitMass.gigaelectronvolt" if massUnit == "GeV" else "UnitMass.baseUnit()" + % massUnitReference = '.megaelectronvoltsPerLightSpeedSquared' if massUnit == 'MeV' else '.gigaelectronvoltsPerLightSpeedSquared' if massUnit == 'GeV' else '.electronvoltsPerLightSpeedSquared' % symbol = flavor[0] struct ${implementation}Tests { ${caseAttribute} func baseMassIs${spelledUnitizedMass}(${caseParameter}) { - #expect(quark.getMass(approximatedBy: .base) == Measurement(value: ${massValue}, unit: ${massUnitReference})) + #expect(quark.getMass(approximatedBy: .base) == ${massUnitReference}(${massValue})) } ${caseAttribute} diff --git a/QuantumMechanicsTests/Measurement+ZeroTests.swift b/QuantumMechanicsTests/Measurement+ZeroTests.swift deleted file mode 100644 index 49aac632..00000000 --- a/QuantumMechanicsTests/Measurement+ZeroTests.swift +++ /dev/null @@ -1,33 +0,0 @@ -// ===-------------------------------------------------------------------------------------------=== -// Copyright © 2025 Supernova. All rights reserved. -// -// This file is part of the Deus open-source project. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the -// GNU General Public License as published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License along with this program. If -// not, see https://www.gnu.org/licenses. -// ===-------------------------------------------------------------------------------------------=== - -import Foundation -import Testing - -@Suite("Measurement+Zero tests") -struct MeasurementZeroTests { - @Test - func valueOfZeroUnitAngleIsZero() { #expect(Measurement.zero.value == 0) } - - @Test - func valueOfZeroUnitElectricChargeIsZero() { - #expect(Measurement.zero.value == 0) - } - - @Test - func valueOfZeroUnitMassIsZero() { #expect(Measurement.zero.value == 0) } -} diff --git a/QuantumMechanicsTests/Measurement+ZeroTests.swift.gyb b/QuantumMechanicsTests/Measurement+ZeroTests.swift.gyb deleted file mode 100644 index 64b13a6d..00000000 --- a/QuantumMechanicsTests/Measurement+ZeroTests.swift.gyb +++ /dev/null @@ -1,39 +0,0 @@ -%{ - # ===------------------------------------------------------------------------------------------=== - # Copyright © 2025 Supernova. All rights reserved. - # - # This file is part of the Deus open-source project. - # - # This program is free software: you can redistribute it and/or modify it under the terms of the - # GNU General Public License as published by the Free Software Foundation, either version 3 of the - # License, or (at your option) any later version. - # - # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - # the GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License along with this program. If - # not, see https://www.gnu.org/licenses. - # ===------------------------------------------------------------------------------------------=== - - import os - - unit_classes = ['UnitAngle', 'UnitElectricCharge', 'UnitMass'] -}% -${os.environ.get('LICENSE_HEADER_SWIFT')} - -import Foundation -import Testing - -@Suite("Measurement+Zero tests") -struct MeasurementZeroTests { - % for index, unit_class in enumerate(unit_classes): - @Test - func valueOfZero${unit_class}IsZero() { - #expect(Measurement<${unit_class}>.zero.value == 0) - } - % if index < len(unit_classes) - 1: - - % end - % end -} diff --git a/QuantumMechanicsTests/MeasurementTests.swift b/QuantumMechanicsTests/MeasurementTests.swift new file mode 100644 index 00000000..27e36831 --- /dev/null +++ b/QuantumMechanicsTests/MeasurementTests.swift @@ -0,0 +1,332 @@ +// ===-------------------------------------------------------------------------------------------=== +// Copyright © 2025 Supernova. All rights reserved. +// +// This file is part of the Deus open-source project. +// +// This program is free software: you can redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with this program. If +// not, see https://www.gnu.org/licenses. +// ===-------------------------------------------------------------------------------------------=== + +import Testing + +@testable import QuantumMechanics + +fileprivate struct AngleTests { + @Test + func callingTheInitializerProducesAngleWhoseQuantityIsInTheBaseUnit() { + let angle = Angle(quantityInBaseUnit: 2) + #expect(angle.quantityInBaseUnit == 2) + #expect(angle.symbol == Angle.baseUnitSymbol) + } + + @Test + func identityIsZeroInBaseUnit() { + #expect(Angle.zero.quantityInCurrentUnit == 0) + #expect(Angle.zero.symbol == Angle.baseUnitSymbol) + } + + @Test(arguments: [UnitRepresentable(unit: \Angle.Type.radians)]) + func negates(in unitRepresentable: UnitRepresentable) { + #expect( + -Angle.self[keyPath: unitRepresentable.unit](2) + == Angle.self[keyPath: unitRepresentable.unit](-2) + ) + } + + @Test(arguments: [(Angle.radians(2), Angle.radians(2))]) + func adds(_ addition: Angle, to measurement: Angle) { + #expect( + measurement + addition + == .init(quantityInBaseUnit: measurement.quantityInBaseUnit + addition.quantityInBaseUnit) + ) + } + + @Test(arguments: [(Angle.radians(2), Angle.radians(2))]) + func subtracts(_ subtraction: Angle, from measurement: Angle) { + #expect( + measurement - subtraction + == .init( + quantityInBaseUnit: measurement.quantityInBaseUnit - subtraction.quantityInBaseUnit + ) + ) + } + + @Test(arguments: zip([Angle.radians(2)], [UnitRepresentable(unit: \Angle.Type.radians)])) + func converts(_ measurement: Angle, into unitRepresentable: UnitRepresentable) { + let converted = measurement.converted(into: unitRepresentable.unit) + #expect( + converted.quantityInBaseUnit == converted.quantityInCurrentUnit + / measurement.conversionCoefficient + ) + } + + @Test(arguments: [UnitRepresentable(unit: \Angle.Type.radians)]) + func isDescribedByQuantityInCurrentUnitAndSymbol(_ unitRepresentable: UnitRepresentable) { + let angle = Angle.self[keyPath: unitRepresentable.unit](2) + #expect( + "\(angle)" == "\(Angle.formatted(quantity: angle.quantityInCurrentUnit)) \(angle.symbol)" + ) + } +} + +fileprivate struct ElectricChargeTests { + @Test + func callingTheInitializerProducesElectricChargeWhoseQuantityIsInTheBaseUnit() { + let electricCharge = ElectricCharge(quantityInBaseUnit: 2) + #expect(electricCharge.quantityInBaseUnit == 2) + #expect(electricCharge.symbol == ElectricCharge.baseUnitSymbol) + } + + @Test + func identityIsZeroInBaseUnit() { + #expect(ElectricCharge.zero.quantityInCurrentUnit == 0) + #expect(ElectricCharge.zero.symbol == ElectricCharge.baseUnitSymbol) + } + + @Test(arguments: [UnitRepresentable(unit: \ElectricCharge.Type.elementary)]) + func negates(in unitRepresentable: UnitRepresentable) { + #expect( + -ElectricCharge.self[keyPath: unitRepresentable.unit](2) + == ElectricCharge.self[keyPath: unitRepresentable.unit](-2) + ) + } + + @Test(arguments: [(ElectricCharge.elementary(2), ElectricCharge.elementary(2))]) + func adds(_ addition: ElectricCharge, to measurement: ElectricCharge) { + #expect( + measurement + addition + == .init(quantityInBaseUnit: measurement.quantityInBaseUnit + addition.quantityInBaseUnit) + ) + } + + @Test(arguments: [(ElectricCharge.elementary(2), ElectricCharge.elementary(2))]) + func subtracts(_ subtraction: ElectricCharge, from measurement: ElectricCharge) { + #expect( + measurement - subtraction + == .init( + quantityInBaseUnit: measurement.quantityInBaseUnit - subtraction.quantityInBaseUnit + ) + ) + } + + @Test( + arguments: zip( + [ElectricCharge.elementary(2)], + [UnitRepresentable(unit: \ElectricCharge.Type.elementary)] + ) + ) + func converts( + _ measurement: ElectricCharge, + into unitRepresentable: UnitRepresentable + ) { + let converted = measurement.converted(into: unitRepresentable.unit) + #expect( + converted.quantityInBaseUnit == converted.quantityInCurrentUnit + / measurement.conversionCoefficient + ) + } + + @Test(arguments: [UnitRepresentable(unit: \ElectricCharge.Type.elementary)]) + func isDescribedByQuantityInCurrentUnitAndSymbol( + _ unitRepresentable: UnitRepresentable + ) { + let electricCharge = ElectricCharge.self[keyPath: unitRepresentable.unit](2) + #expect( + "\(electricCharge)" + == "\(ElectricCharge.formatted(quantity: electricCharge.quantityInCurrentUnit)) \(electricCharge.symbol)" + ) + } +} + +fileprivate struct MassTests { + @Test + func callingTheInitializerProducesMassWhoseQuantityIsInTheBaseUnit() { + let mass = Mass(quantityInBaseUnit: 2) + #expect(mass.quantityInBaseUnit == 2) + #expect(mass.symbol == Mass.baseUnitSymbol) + } + + @Test + func identityIsZeroInBaseUnit() { + #expect(Mass.zero.quantityInCurrentUnit == 0) + #expect(Mass.zero.symbol == Mass.baseUnitSymbol) + } + + @Test(arguments: [ + UnitRepresentable(unit: \Mass.Type.electronvoltsPerLightSpeedSquared), + .init(unit: \.megaelectronvoltsPerLightSpeedSquared), + .init(unit: \.gigaelectronvoltsPerLightSpeedSquared) + ]) + func negates(in unitRepresentable: UnitRepresentable) { + #expect( + -Mass.self[keyPath: unitRepresentable.unit](2) + == Mass.self[keyPath: unitRepresentable.unit](-2) + ) + } + + @Test(arguments: [ + (Mass.electronvoltsPerLightSpeedSquared(2), Mass.electronvoltsPerLightSpeedSquared(2)), + (.electronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2)), + (.electronvoltsPerLightSpeedSquared(2), .gigaelectronvoltsPerLightSpeedSquared(2)), + (.megaelectronvoltsPerLightSpeedSquared(2), .electronvoltsPerLightSpeedSquared(2)), + (.megaelectronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2)), + (.megaelectronvoltsPerLightSpeedSquared(2), .gigaelectronvoltsPerLightSpeedSquared(2)), + (.gigaelectronvoltsPerLightSpeedSquared(2), .electronvoltsPerLightSpeedSquared(2)), + (.gigaelectronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2)), + (.gigaelectronvoltsPerLightSpeedSquared(2), .gigaelectronvoltsPerLightSpeedSquared(2)) + ]) + func adds(_ addition: Mass, to measurement: Mass) { + #expect( + measurement + addition + == .init(quantityInBaseUnit: measurement.quantityInBaseUnit + addition.quantityInBaseUnit) + ) + } + + @Test(arguments: [ + (Mass.electronvoltsPerLightSpeedSquared(2), Mass.electronvoltsPerLightSpeedSquared(2)), + (.electronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2)), + (.electronvoltsPerLightSpeedSquared(2), .gigaelectronvoltsPerLightSpeedSquared(2)), + (.megaelectronvoltsPerLightSpeedSquared(2), .electronvoltsPerLightSpeedSquared(2)), + (.megaelectronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2)), + (.megaelectronvoltsPerLightSpeedSquared(2), .gigaelectronvoltsPerLightSpeedSquared(2)), + (.gigaelectronvoltsPerLightSpeedSquared(2), .electronvoltsPerLightSpeedSquared(2)), + (.gigaelectronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2)), + (.gigaelectronvoltsPerLightSpeedSquared(2), .gigaelectronvoltsPerLightSpeedSquared(2)) + ]) + func subtracts(_ subtraction: Mass, from measurement: Mass) { + #expect( + measurement - subtraction + == .init( + quantityInBaseUnit: measurement.quantityInBaseUnit - subtraction.quantityInBaseUnit + ) + ) + } + + @Test( + arguments: zip( + [ + Mass.electronvoltsPerLightSpeedSquared(2), .megaelectronvoltsPerLightSpeedSquared(2), + .gigaelectronvoltsPerLightSpeedSquared(2) + ], + [ + UnitRepresentable(unit: \Mass.Type.electronvoltsPerLightSpeedSquared), + .init(unit: \.megaelectronvoltsPerLightSpeedSquared), + .init(unit: \.gigaelectronvoltsPerLightSpeedSquared) + ] + ) + ) + func converts(_ measurement: Mass, into unitRepresentable: UnitRepresentable) { + let converted = measurement.converted(into: unitRepresentable.unit) + #expect( + converted.quantityInBaseUnit == converted.quantityInCurrentUnit + / measurement.conversionCoefficient + ) + } + + @Test(arguments: [ + UnitRepresentable(unit: \Mass.Type.electronvoltsPerLightSpeedSquared), + .init(unit: \.megaelectronvoltsPerLightSpeedSquared), + .init(unit: \.gigaelectronvoltsPerLightSpeedSquared) + ]) + func isDescribedByQuantityInCurrentUnitAndSymbol(_ unitRepresentable: UnitRepresentable) { + let mass = Mass.self[keyPath: unitRepresentable.unit](2) + #expect("\(mass)" == "\(Mass.formatted(quantity: mass.quantityInCurrentUnit)) \(mass.symbol)") + } +} + +fileprivate struct SpeedTests { + @Test + func callingTheInitializerProducesSpeedWhoseQuantityIsInTheBaseUnit() { + let speed = Speed(quantityInBaseUnit: 2) + #expect(speed.quantityInBaseUnit == 2) + #expect(speed.symbol == Speed.baseUnitSymbol) + } + + @Test + func identityIsZeroInBaseUnit() { + #expect(Speed.zero.quantityInCurrentUnit == 0) + #expect(Speed.zero.symbol == Speed.baseUnitSymbol) + } + + @Test(arguments: [UnitRepresentable(unit: \Speed.Type.metersPerSecond)]) + func negates(in unitRepresentable: UnitRepresentable) { + #expect( + -Speed.self[keyPath: unitRepresentable.unit](2) + == Speed.self[keyPath: unitRepresentable.unit](-2) + ) + } + + @Test(arguments: [(Speed.metersPerSecond(2), Speed.metersPerSecond(2))]) + func adds(_ addition: Speed, to measurement: Speed) { + #expect( + measurement + addition + == .init(quantityInBaseUnit: measurement.quantityInBaseUnit + addition.quantityInBaseUnit) + ) + } + + @Test(arguments: [(Speed.metersPerSecond(2), Speed.metersPerSecond(2))]) + func subtracts(_ subtraction: Speed, from measurement: Speed) { + #expect( + measurement - subtraction + == .init( + quantityInBaseUnit: measurement.quantityInBaseUnit - subtraction.quantityInBaseUnit + ) + ) + } + + @Test( + arguments: zip( + [Speed.metersPerSecond(2)], + [UnitRepresentable(unit: \Speed.Type.metersPerSecond)] + ) + ) + func converts(_ measurement: Speed, into unitRepresentable: UnitRepresentable) { + let converted = measurement.converted(into: unitRepresentable.unit) + #expect( + converted.quantityInBaseUnit == converted.quantityInCurrentUnit + / measurement.conversionCoefficient + ) + } + + @Test(arguments: [UnitRepresentable(unit: \Speed.Type.metersPerSecond)]) + func isDescribedByQuantityInCurrentUnitAndSymbol(_ unitRepresentable: UnitRepresentable) { + let speed = Speed.self[keyPath: unitRepresentable.unit](2) + #expect( + "\(speed)" == "\(Speed.formatted(quantity: speed.quantityInCurrentUnit)) \(speed.symbol)" + ) + } +} + +/// Representation of an SI unit in which a quantity of a ``Measurement`` may be. Exists merely for +/// debugging purposes, allowing for displaying the actual name of the unit while testing rather +/// than the string of the key path. +private struct UnitRepresentable: @unchecked Sendable +where MeasurementType: Measurement { + /// The unit of the specified ``Measurement`` being represented. + let unit: MeasurementType.Unit +} + +extension UnitRepresentable: CustomDebugStringConvertible { + var debugDescription: String { + switch unit { + case _ where unit == \ElectricCharge.Type.elementary: "elementary" + case _ where unit == \Speed.Type.metersPerSecond: "metersPerSecond" + case _ where unit == \Mass.Type.gigaelectronvoltsPerLightSpeedSquared: + "gigaelectronvoltsPerLightSpeedSquared" + case _ where unit == \Angle.Type.radians: "radians" + case _ where unit == \Mass.Type.electronvoltsPerLightSpeedSquared: + "electronvoltsPerLightSpeedSquared" + case _ where unit == \Mass.Type.megaelectronvoltsPerLightSpeedSquared: + "megaelectronvoltsPerLightSpeedSquared" + default: "Unknown instance of UnitRepresentable<\(MeasurementType.self)>" + } + } +} diff --git a/QuantumMechanicsTests/MeasurementTests.swift.gyb b/QuantumMechanicsTests/MeasurementTests.swift.gyb new file mode 100644 index 00000000..6caece39 --- /dev/null +++ b/QuantumMechanicsTests/MeasurementTests.swift.gyb @@ -0,0 +1,168 @@ +%{ + # ===------------------------------------------------------------------------------------------=== + # Copyright © 2025 Supernova. All rights reserved. + # + # This file is part of the Deus open-source project. + # + # This program is free software: you can redistribute it and/or modify it under the terms of the + # GNU General Public License as published by the Free Software Foundation, either version 3 of the + # License, or (at your option) any later version. + # + # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + # the GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License along with this program. If + # not, see https://www.gnu.org/licenses. + # ===------------------------------------------------------------------------------------------=== + + import os + import re + + measurements = [ + { + 'base_unit_symbol': 'rad', + 'conversion': {'radians': 1}, + 'implementation_type': 'Angle' + }, + { + 'base_unit_symbol': 'e', + 'conversion': {'elementary': 1}, + 'implementation_type': 'ElectricCharge' + }, + { + 'base_unit_symbol': 'kg', + 'conversion': { + 'electronvoltsPerLightSpeedSquared': 1, + 'megaelectronvoltsPerLightSpeedSquared': 1e-12, + 'gigaelectronvoltsPerLightSpeedSquared': 1e-6 + }, + 'implementation_type': 'Mass' + }, + { + 'base_unit_symbol': 'm/s', + 'conversion': {'metersPerSecond': 1}, + 'implementation_type': 'Speed' + } + ] + """Information about the measurements to be tested. All implementations of the `Measurement` + protocol should be included here. + + Each map in the array contains the following key–value pairs (which are mandatory): + + - `base_unit_symbol` + Symbol of the base unit of the measurement. Same as that returned by + `UnitConvertible/baseUnitSymbol`. + - `conversion` + Relation of the name of each unit to the conversion coefficient of such unit. The conversion + coefficient is that returned by the `UnitConvertible/conversionCoefficient` property of the + measurement. + - `implementation_type` + Name of the struct to which the map refers and by which the `Measurement` protocol is + implemented. + """ + + def decapitalized(str): + """Lower-cases the first character of the given string. + + :param str: The string to be decapitalized. + :returns The given string, decapitalized. + """ + + if not str: return str + decapitalized = str[0].lower() + str[1:] + return decapitalized + + def partialize(index, access): + """Converts a given Swift access from full, prefixed by the name of the enclosing type, into + partial, in which such prefix is omitted. + + These nomenclatures ("full and partial access") do not necessarily reflect that employed by + Swift in the documentation or the compiler. They are Deus-specific, and compose yet another + type of access: contextual. An array of contextual accesses contains, in order, a full access + followed by partial ones. + + :param index: Index of the access in the array. + :param access: The access, in Swift, to a property or function of a type. + """ + if index == 0 or access.startswith('.'): return access + return '.' + '.'.join(access.split('.', 1)[1:]) +}% +${os.environ.get('LICENSE_HEADER_SWIFT')} + +import Testing + +@testable import QuantumMechanics + +% for measure_index, measurement in enumerate(measurements): + % implementation_type = measurement['implementation_type'] + % variable_name = decapitalized(implementation_type) + % unit_names = measurement['conversion'].keys() + % unit_representable_type = 'UnitRepresentable<' + implementation_type + '>' + % unit_representables = map(lambda (index, unit_name): ('UnitRepresentable(unit: \\' + implementation_type + '.Type.' if index == 0 else '.init(unit: \.') + unit_name + ')', enumerate(unit_names)) + % full_unit_accesses = list(map(lambda unit_name: implementation_type + '.' + unit_name, unit_names)) + % contextual_unit_accesses = [full_unit_accesses[0]] + map(lambda unit_name: '.' + unit_name, unit_names[1:]) + % unit_representables_expression = '[' + ', '.join(unit_representables) + ']' + % measurement_combinations_expression = '[' + ', '.join(map(lambda (key_index, key): ', '.join(map(lambda (value_index, value): '(' + partialize(value_index, key) + '(2), ' + partialize(key_index, value) + '(2))', enumerate(contextual_unit_accesses))), enumerate(contextual_unit_accesses))) + ']' + % measurements_expression = '[' + ', '.join(map(lambda unit_name: unit_name + '(2)', contextual_unit_accesses)) + ']' +fileprivate struct ${implementation_type}Tests { + @Test + func callingTheInitializerProduces${implementation_type}WhoseQuantityIsInTheBaseUnit() { + let ${variable_name} = ${implementation_type}(quantityInBaseUnit: 2) + #expect(${variable_name}.quantityInBaseUnit == 2) + #expect(${variable_name}.symbol == ${implementation_type}.baseUnitSymbol) + } + + @Test + func identityIsZeroInBaseUnit() { + #expect(${implementation_type}.zero.quantityInCurrentUnit == 0) + #expect(${implementation_type}.zero.symbol == ${implementation_type}.baseUnitSymbol) + } + + @Test(arguments: ${unit_representables_expression}) + func negates(in unitRepresentable: ${unit_representable_type}) { + #expect(-${implementation_type}.self[keyPath: unitRepresentable.unit](2) == ${implementation_type}.self[keyPath: unitRepresentable.unit](-2)) + } + + @Test(arguments: ${measurement_combinations_expression}) + func adds(_ addition: ${implementation_type}, to measurement: ${implementation_type}) { + #expect(measurement + addition == .init(quantityInBaseUnit: measurement.quantityInBaseUnit + addition.quantityInBaseUnit)) + } + + @Test(arguments: ${measurement_combinations_expression}) + func subtracts(_ subtraction: ${implementation_type}, from measurement: ${implementation_type}) { + #expect(measurement - subtraction == .init(quantityInBaseUnit: measurement.quantityInBaseUnit - subtraction.quantityInBaseUnit)) + } + + @Test(arguments: zip(${measurements_expression}, ${unit_representables_expression})) + func converts(_ measurement: ${implementation_type}, into unitRepresentable: ${unit_representable_type}) { + let converted = measurement.converted(into: unitRepresentable.unit) + #expect(converted.quantityInBaseUnit == converted.quantityInCurrentUnit / measurement.conversionCoefficient) + } + + @Test(arguments: ${unit_representables_expression}) + func isDescribedByQuantityInCurrentUnitAndSymbol(_ unitRepresentable: ${unit_representable_type}) { + let ${variable_name} = ${implementation_type}.self[keyPath: unitRepresentable.unit](2) + #expect("\(${variable_name})" == "\(${implementation_type}.formatted(quantity: ${variable_name}.quantityInCurrentUnit)) \(${variable_name}.symbol)") + } +} + +% end +/// Representation of an SI unit in which a quantity of a ``Measurement`` may be. Exists merely for +/// debugging purposes, allowing for displaying the actual name of the unit while testing rather +/// than the string of the key path. +private struct UnitRepresentable: @unchecked Sendable where MeasurementType: Measurement { + /// The unit of the specified ``Measurement`` being represented. + let unit: MeasurementType.Unit +} + +extension UnitRepresentable: CustomDebugStringConvertible { + var debugDescription: String { + switch unit { +% for implementation_type, unit_name in {(measurement['implementation_type'], unit_name) for measurement in measurements for unit_name in measurement['conversion'].keys()}: + case _ where unit == \${implementation_type}.Type.${unit_name}: "${unit_name}" +% end + default: "Unknown instance of UnitRepresentable<\(MeasurementType.self)>" + } + } +} diff --git a/QuantumMechanicsTests/SymmetryTests.swift b/QuantumMechanicsTests/SymmetryTests.swift index 8a4719ac..7b2625d9 100644 --- a/QuantumMechanicsTests/SymmetryTests.swift +++ b/QuantumMechanicsTests/SymmetryTests.swift @@ -15,7 +15,6 @@ // not, see https://www.gnu.org/licenses. // ===-------------------------------------------------------------------------------------------=== -import Foundation import Numerics import Testing @@ -29,19 +28,15 @@ struct SymmetryTests { #expect(Complex(2, 4).u1(by: .zero) == Complex(2, 4)) } - @Test( - arguments: stride(from: 2, to: 64, by: 2).map { - Measurement(value: .pi * $0, unit: UnitAngle.radians) - } - ) - func fieldIsUntransformedUponFullTurn(of angle: Measurement) { + @Test(arguments: stride(from: 2, to: 64, by: 2).map { Angle.radians(.pi * $0) }) + func fieldIsUntransformedUponFullTurn(of angle: Angle) { #expect(Complex(2, 4).u1(by: angle).isApproximatelyEqual(to: Complex(2, 4))) } @Test func fieldIsTransformedWhenRotatedByNonGroupIdentity() { #expect( - Complex(2, 4).u1(by: Measurement(value: 2, unit: UnitAngle.radians)).isApproximatelyEqual( + Complex(2, 4).u1(by: .radians(2)).isApproximatelyEqual( to: Complex(-4.46, 0.15), relativeTolerance: 0.01 )