import Foundation import Accelerate public final class PCA: Codable, @unchecked Sendable { public let inputDimension: Int public let outputDimension: Int public let mean: [Float] public let components: [[Float]] public let explainedVariance: [Float] public let whiteningEnabled: Bool public let sampleCount: Int private let trainingData: [[Float]] enum CodingKeys: String, CodingKey { case inputDimension, outputDimension, mean, components, explainedVariance, whiteningEnabled, sampleCount, trainingData } public init(inputDimension: Int, outputDimension: Int, mean: [Float], components: [[Float]], explainedVariance: [Float], whiteningEnabled: Bool = false, sampleCount: Int = 0, trainingData: [[Float]] = []) { self.inputDimension = inputDimension self.outputDimension = outputDimension self.mean = mean self.components = components self.explainedVariance = explainedVariance self.whiteningEnabled = whiteningEnabled self.sampleCount = sampleCount self.trainingData = trainingData } public func transform(_ input: [Float]) throws -> [Float] { guard input.count == inputDimension else { throw PCAError.dimensionMismatch } var centered = [Float](repeating: 0, count: inputDimension) for i in 0.. [Float] { var result = try transform(input) for j in 0.. [Float] { let total = explainedVariance.reduce(0, +) guard total > 0 else { return explainedVariance.map { _ in 0 } } return explainedVariance.map { $0 / total } } public func cumulativeExplainedVarianceRatio() -> [Float] { let ratios = explainedVarianceRatio() var cum: [Float] = [] var sum: Float = 0 for r in ratios { sum += r cum.append(sum) } return cum } public func save(to url: URL) throws { let encoder = JSONEncoder() let data = try encoder.encode(self) try data.write(to: url) } public static func load(from url: URL) throws -> PCA { let data = try Data(contentsOf: url) let decoder = JSONDecoder() return try decoder.decode(PCA.self, from: data) } public static func train(data: [[Float]], outputDimension: Int, whitening: Bool = false) throws -> PCA { guard let first = data.first else { throw PCAError.noData } let n = data.count let d = first.count let k = min(outputDimension, d, n) guard k > 0 else { throw PCAError.invalidDimension } var mean = [Float](repeating: 0, count: d) for i in 0.. PCA { let combined = trainingData + newSamples return try PCA.train(data: combined, outputDimension: outputDimension, whitening: whiteningEnabled) } public func partialFit(sample: [Float]) throws -> PCA { return try incrementalUpdate(newSamples: [sample]) } } public enum PCAError: Error, LocalizedError { case noData case invalidDimension case dimensionMismatch case svdFailed public var errorDescription: String? { switch self { case .noData: return "No data provided for PCA training" case .invalidDimension: return "Invalid output dimension" case .dimensionMismatch: return "Input dimension does not match model" case .svdFailed: return "SVD computation failed" } } }