Skip to content

Commit 52b50ea

Browse files
authored
Merge pull request #380 from JosuaCarl/fix-polynomial-functions
Fix: Fixed application of custom cpu power model
2 parents 9cc8653 + 69670db commit 52b50ea

6 files changed

Lines changed: 28 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# New
22
## Bug Fixes:
3+
- CPU power model now applied correctly
34
- Unintended report value removal
45

56
## Misc:

docs/usage/parameters.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ The following parameters are currently available:
154154

155155
!!! warning "Experimental feature"
156156
The `cpuPowerModel` parameter is experimental and may change in future releases.
157+
158+
A power model function that takes the parameter `coreUsage`.
157159

158-
Polynomial coefficients for a custom CPU power model (highest degree first).
159-
160-
If specified, this overrides TDP-based power draw estimation for CPU cores. The coefficients define a function that returns the **per-core power draw** (in Watts) as a function of core utilization (0–1).
160+
If specified, this overrides TDP-based power draw estimation for CPU cores. The function returns the **per-core power draw** (in Watts) as a function of core utilization (0–1).
161161

162-
**Example**: `[0.5, 10.0]` defines the model `0.5 × coreUsage + 10`
162+
**Example**: `{coreUsage -> 0.5 * coreUsage + 10.0}`
163163

164164
**Example visualization**:
165165

src/main/nextflow/co2footprint/CO2FootprintCalculator.groovy

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,7 @@ class CO2FootprintCalculator {
8181
final BigDecimal coreUsage = cpuUsage / (100.0 * numberOfCores)
8282

8383
// Per-core power draw: either custom polynomial model or TDP lookup [W/core]
84-
final List<Number> cpuPowerModel = config.cpuPowerModel
85-
final BigDecimal powerdrawPerCore = cpuPowerModel ? getPowerDrawFromModel(cpuPowerModel, coreUsage) : tdpDataMatrix.matchModel(cpuModel).getLogicalCoreTDP()
84+
final BigDecimal powerdrawPerCore = tdpDataMatrix.matchModel(cpuModel).getLogicalCoreTDP()
8685

8786
/* ===== Memory Information ===== */
8887

@@ -127,7 +126,13 @@ class CO2FootprintCalculator {
127126
/* ===== Energy & Emission Calculation ===== */
128127

129128
// Energy consumption [kWh]
130-
BigDecimal rawEnergyProcessor = runtime_h * numberOfCores * powerdrawPerCore * coreUsage * 0.001
129+
BigDecimal rawEnergyProcessor
130+
if (config.cpuPowerModel) {
131+
rawEnergyProcessor = runtime_h * numberOfCores * config.cpuPowerModel(coreUsage) * 0.001
132+
}
133+
else {
134+
rawEnergyProcessor = runtime_h * numberOfCores * powerdrawPerCore * coreUsage * 0.001
135+
}
131136
BigDecimal rawEnergyMemory = runtime_h * memory * powerdrawMem * 0.001
132137
BigDecimal energy = pue * (rawEnergyProcessor + rawEnergyMemory)
133138

@@ -202,23 +207,4 @@ class CO2FootprintCalculator {
202207
}
203208
return value != null ? value : defaultValue
204209
}
205-
206-
/**
207-
* Computes CPU power draw using the configured polynomial model.
208-
*
209-
* @param coefficients List of polynomial coefficients (highest degree first), as Double or BigDecimal.
210-
* @param coreUsage CPU usage as a fraction between 0 and 1.
211-
* @return Estimated power draw [W/core], or null if no model configured.
212-
*/
213-
static BigDecimal getPowerDrawFromModel(List<Number> coefficients, BigDecimal coreUsage) {
214-
BigDecimal power = 0.0
215-
Integer degree = coefficients.size() - 1
216-
217-
coefficients.eachWithIndex { Number c, Integer i ->
218-
power += (c as BigDecimal) * coreUsage ** (degree - i)
219-
}
220-
221-
return power
222-
}
223-
224210
}

src/main/nextflow/co2footprint/CO2FootprintConfig.groovy

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ class CO2FootprintConfig implements ConfigScope {
114114
String machineType
115115

116116
@ConfigOption
117-
@Description('Polynomial coefficients for CPU power model (highest degree first).')
118-
final List<Number> cpuPowerModel
117+
@Description('A power model function that takes the parameter `coreUsage`.')
118+
final Closure<Number> cpuPowerModel
119119

120120
/**
121121
* Loads configuration from a map and sets up defaults and fallbacks.
@@ -149,12 +149,9 @@ class CO2FootprintConfig implements ConfigScope {
149149
ciMarket = getCollect('ciMarket', configMap, usedKeys) as BigDecimal
150150

151151
// Power model
152-
cpuPowerModel = getCollect('cpuPowerModel', configMap, usedKeys) as List<BigDecimal>
152+
cpuPowerModel = getCollect('cpuPowerModel', configMap, usedKeys) as Closure<BigDecimal>
153153
if (cpuPowerModel != null) {
154-
Integer degree = cpuPowerModel.size() - 1
155-
List<String> terms = []
156-
cpuPowerModel.eachWithIndex { Number c, Integer i -> terms.add("${c}*x^${degree - i}") }
157-
log.info("Using custom CPU power model: f(x) = " + terms.join(" + "))
154+
log.info("Using custom CPU power model.")
158155
}
159156

160157
// Powerdraw factors

src/test/nextflow/co2footprint/CO2FootprintCalculatorTest.groovy

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class CO2FootprintCalculatorTest extends Specification{
3737
def traceRecord = new TraceRecord()
3838
traceRecord.task_id = '1'
3939
traceRecord.realtime = (1 as Long) * (3600000 as Long)
40-
traceRecord.cpus = 1
40+
traceRecord.cpus = 2
4141
traceRecord.cpu_model = cpuModel
4242
traceRecord.'%cpu' = 100.0
4343
traceRecord.memory = (7 as Long) * (1024**3 as Long)
@@ -52,13 +52,13 @@ class CO2FootprintCalculatorTest extends Specification{
5252
round(co2Record.store.CO2e as Double) == expectedCO2
5353

5454
where:
55-
cpuModel | configMap || expectedEnergy | expectedCO2
56-
"Unknown model" | [:] || 14.06 | 6.75
57-
"AMD EPYC 7251" | [:] || 10.11 | 4.85
58-
"Unknown model" | [pue: 1.4] || 19.68 | 9.45
59-
"Unknown model" | [location: 'DE'] || 14.06 | 4.69
60-
"Unknown model" | [ci: 338.66] || 14.06 | 4.76
61-
"AMD EPYC 7251" | [cpuPowerModel: [0.5d, 10.0d]] || 13.11 | 6.29
55+
cpuModel | configMap || expectedEnergy | expectedCO2
56+
"Unknown model" | [:] || 14.06 | 6.75
57+
"AMD EPYC 7251" | [:] || 10.11 | 4.85
58+
"Unknown model" | [pue: 1.4] || 19.68 | 9.45
59+
"Unknown model" | [location: 'DE'] || 14.06 | 4.69
60+
"Unknown model" | [ci: 338.66] || 14.06 | 4.76
61+
"AMD EPYC 7251" | [cpuPowerModel: {coreUsage -> 0.5 * coreUsage + 10.0}] || 23.11 | 11.09
6262
}
6363

6464
// ------ Equivalences Calculation ------
@@ -125,25 +125,7 @@ class CO2FootprintCalculatorTest extends Specification{
125125
4L*1024**3 | null | false | 4L // requested used (required null)
126126
null | null | true | null // throws error (both null)
127127
}
128-
129-
def 'test power draw from polynomial model'() {
130-
given:
131-
def computer = new CO2FootprintCalculator(null, null)
132-
133-
expect:
134-
computer.getPowerDrawFromModel(coeffs, usage as BigDecimal).round(6) == expected
135-
136-
where:
137-
coeffs | usage || expected
138-
[2.0, 5.0] | 0 || 5.0 // 2*x + 5 at x=0
139-
[2.0, 5.0] | 50 || 105.0 // 2*50 + 5
140-
[2.0, 5.0] | 100 || 205.0 // 2*100 + 5
141-
[1.0, 0.0, 0.0] | 2 || 4.0 // x² at x=2
142-
[1.0, 0.0, 0.0] | 5 || 25.0 // x² at x=5
143-
[0.5, 10.0] | 20 || 20.0 // 0.5*20 + 10
144-
[BigDecimal.valueOf(1), 5] | 3 || 8.0 // supports BigDecimal coeffs too
145-
}
146-
128+
147129
def "Determination of number of CPUs"() {
148130
given:
149131
def traceRecord = new TraceRecord()

src/test/nextflow/co2footprint/CO2FootprintConfigTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class CO2FootprintConfigTest extends Specification {
127127

128128
def 'should log custom CPU power model as polynomial'() {
129129
given:
130-
def configMap = [cpuPowerModel: [2.5, 1.3, 0.7], machineType: 'local']
130+
def configMap = [cpuPowerModel: {x -> 2.5 * x**2 + 1.3 * x + 0.7}, machineType: 'local']
131131
def processMap = [:]
132132

133133
// Set up log capturing
@@ -141,7 +141,7 @@ class CO2FootprintConfigTest extends Specification {
141141

142142
then:
143143
List<String> logMessages = listAppender.list*.formattedMessage
144-
logMessages.any {String message -> message.contains("Using custom CPU power model: f(x) = 2.5*x^2 + 1.3*x^1 + 0.7*x^0") }
144+
logMessages.any {String message -> message.contains("Using custom CPU power model.") }
145145
}
146146

147147
// Helper method to validate default properties

0 commit comments

Comments
 (0)