Skip to content

Commit f3b7b6a

Browse files
committed
Initial commit
0 parents  commit f3b7b6a

7 files changed

Lines changed: 269 additions & 0 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// swift-tools-version: 5.10
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "LoopAlgorithmToPython",
8+
platforms: [
9+
.macOS(.v13),
10+
.iOS(.v15),
11+
.tvOS(.v15),
12+
.watchOS(.v8)
13+
],
14+
products: [
15+
// Products define the executables and libraries a package produces, making them visible to other packages.
16+
.library(
17+
name: "LoopAlgorithmToPython",
18+
type: .dynamic,
19+
targets: ["LoopAlgorithmToPython"]),
20+
],
21+
dependencies: [
22+
.package(url: "https://github.com/tidepool-org/LoopAlgorithm.git", branch: "main"),
23+
],
24+
targets: [
25+
// Targets are the basic building blocks of a package, defining a module or a test suite.
26+
// Targets can depend on other targets in this package and products from dependencies.
27+
.target(
28+
name: "LoopAlgorithmToPython",
29+
dependencies: ["LoopAlgorithm"]
30+
),
31+
.testTarget(
32+
name: "LoopAlgorithmToPythonTests",
33+
dependencies: ["LoopAlgorithmToPython"]),
34+
]
35+
)

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# LoopAlgorithmToPython
2+
3+
This Swift module uses LoopAlgorithm to create C functions for generating predictions and prediction dates from JSON data.
4+
5+
How?
6+
I create a foreign function interface (FFI) in Swift by using the unofficial @_cdecl Swift function. This interfaces the Swift code with C. Then we can create a dynamic library, import it into a Python (or other) repositories, and use for example ctypes to compile the C code.
7+
8+
9+
## Installation
10+
11+
1. Clone the repository
12+
2. Build the dynamic library:
13+
14+
```
15+
swift package clean
16+
swift build --configuration release
17+
```
18+
Check if the dynamic library got properly generated and print the path:
19+
```
20+
find .build -name "libLoopAlgorithmToPython.dylib"
21+
```
22+
Output should be something like: /release/libLoopAlgorithmToPython.dylib
23+
24+
Copy that file into your repository.
25+
26+
27+
## Exposed functions
28+
29+
You can find the C-exposed functions in the file `LoopAlgorithmToPython.swift`.
30+
31+
32+
33+
## Usage in Python
34+
35+
Here's how you can use the dynamic library (`libLoopAlgorithmToPython.dylib`) in Python to call the exposed functions:
36+
37+
```
38+
import ctypes
39+
import json
40+
41+
json_file_path = 'some_file.json'
42+
43+
# Load the shared library
44+
swift_lib = ctypes.CDLL('./libLoopAlgorithmToPython.dylib')
45+
46+
# Specify the argument types and return type of the Swift function
47+
swift_lib.generatePrediction.argtypes = [ctypes.c_char_p]
48+
swift_lib.generatePrediction.restype = ctypes.POINTER(ctypes.c_double)
49+
50+
# Read JSON file
51+
def read_json_file(file_path):
52+
with open(file_path, 'r') as f:
53+
data = json.load(f)
54+
return data
55+
56+
json_data = read_json_file(json_file_path) # Read JSON file
57+
json_str = json.dumps(json_data) # Convert JSON data to JSON string
58+
json_bytes = json_str.encode('utf-8') # Convert JSON string to bytes
59+
60+
# Prepare a variable to receive the length of the predicted values
61+
length = 82
62+
63+
# Call the Swift function
64+
result = swift_lib.generatePrediction(json_bytes)
65+
66+
# Read the generated predictions
67+
array = [result[i] for i in range(length)]
68+
print(array[0])
69+
print(f"The result from generatePrediction is: {array}")
70+
71+
# Specify the argument types and return type of the prediction dates
72+
swift_lib.getPredictionDates.argtypes = [ctypes.c_char_p]
73+
swift_lib.getPredictionDates.restype = ctypes.c_char_p
74+
75+
# Call the Swift function
76+
result = swift_lib.getPredictionDates(json_bytes).decode('utf-8')
77+
date_list = result.split(',')[:-1]
78+
print(f"The result from getPredictionDates is: {date_list}")
79+
```
80+
81+
Adjust the paths, function names, and details as per your specific project setup and requirements.
82+
83+
84+
85+
86+
87+
88+
89+
90+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//
2+
// LoopAlgorithmToPython.swift
3+
// LoopAlgorithm
4+
//
5+
// Created by Miriam K. Wolff on 24/06/2024.
6+
//
7+
8+
import Foundation
9+
import LoopAlgorithm
10+
import HealthKit
11+
12+
@_cdecl("generatePrediction") // Use @_cdecl to expose the function with a C-compatible name
13+
public func generatePrediction(jsonData: UnsafePointer<Int8>?) -> UnsafeMutablePointer<Double> {
14+
/// To generate a lib file, run:
15+
/// swiftc -emit-library -o libMySwiftModule.dylib Sources/LoopAlgorithmToPython/LoopAlgorithmToPython.swift
16+
17+
// TODO: Add opportunity to get prediction effects from only one factor at a time
18+
let decoder = JSONDecoder()
19+
decoder.dateDecodingStrategy = .iso8601
20+
21+
guard let jsonData = jsonData else {
22+
fatalError("No JSON data provided")
23+
}
24+
25+
// Convert JSON data to Data
26+
let data = Data(bytes: jsonData, count: strlen(jsonData))
27+
28+
do {
29+
// Decode JSON data
30+
let input = try decoder.decode(LoopPredictionInput.self, from: data)
31+
32+
let prediction = LoopAlgorithm.generatePrediction(
33+
start: input.glucoseHistory.last?.startDate ?? Date(),
34+
glucoseHistory: input.glucoseHistory,
35+
doses: input.doses,
36+
carbEntries: input.carbEntries,
37+
basal: input.basal,
38+
sensitivity: input.sensitivity,
39+
carbRatio: input.carbRatio,
40+
algorithmEffectsOptions: .all, // Here we can adjust which predictive factor to output
41+
useIntegralRetrospectiveCorrection: input.useIntegralRetrospectiveCorrection
42+
)
43+
44+
var predictedValues: [Double] = []
45+
46+
for val in prediction.glucose {
47+
predictedValues.append(val.quantity.doubleValue(for: HKUnit(from: "mg/dL")))
48+
}
49+
let pointer = UnsafeMutablePointer<Double>.allocate(capacity: predictedValues.count)
50+
pointer.initialize(from: predictedValues, count: predictedValues.count)
51+
52+
return pointer
53+
} catch {
54+
fatalError("Error reading or decoding JSON file: \(error)")
55+
}
56+
}
57+
58+
@_cdecl("getPredictionDates") // Use @_cdecl to expose the function with a C-compatible name
59+
public func getPredictionDates(jsonData: UnsafePointer<Int8>?) -> UnsafePointer<CChar> {
60+
/// To generate a lib file, run:
61+
/// swiftc -emit-library -o libMySwiftModule.dylib Sources/LoopAlgorithmToPython/LoopAlgorithmToPython.swift
62+
63+
let decoder = JSONDecoder()
64+
decoder.dateDecodingStrategy = .iso8601
65+
66+
guard let jsonData = jsonData else {
67+
fatalError("No JSON data provided")
68+
}
69+
70+
// Convert JSON data to Data
71+
let data = Data(bytes: jsonData, count: strlen(jsonData))
72+
73+
do {
74+
// Decode JSON data
75+
let input = try decoder.decode(LoopPredictionInput.self, from: data)
76+
77+
let prediction = LoopAlgorithm.generatePrediction(
78+
start: input.glucoseHistory.last?.startDate ?? Date(),
79+
glucoseHistory: input.glucoseHistory,
80+
doses: input.doses,
81+
carbEntries: input.carbEntries,
82+
basal: input.basal,
83+
sensitivity: input.sensitivity,
84+
carbRatio: input.carbRatio,
85+
algorithmEffectsOptions: .all,
86+
useIntegralRetrospectiveCorrection: input.useIntegralRetrospectiveCorrection
87+
)
88+
// Prepare prediction dates as a comma-separated string
89+
var predictionDates: String = ""
90+
for val in prediction.glucose {
91+
predictionDates += val.startDate.ISO8601Format() + ","
92+
}
93+
let cString = strdup(predictionDates)!
94+
95+
return UnsafePointer<CChar>(cString)
96+
} catch {
97+
fatalError("Error reading or decoding JSON file: \(error)")
98+
}
99+
}
100+
101+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import XCTest
2+
@testable import LoopAlgorithmToPython
3+
4+
final class LoopAlgorithmToPythonTests: XCTestCase {
5+
func testExample() throws {
6+
// XCTest Documentation
7+
// https://developer.apple.com/documentation/xctest
8+
9+
// Defining Test Cases and Test Methods
10+
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11+
}
12+
}

0 commit comments

Comments
 (0)