• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oemof / oemof-solph / 10504189510

22 Aug 2024 08:03AM UTC coverage: 88.443% (-0.2%) from 88.63%
10504189510

Pull #1112

github

web-flow
Merge cd0b8b530 into 5706ca2e0
Pull Request #1112: Revision/remove v0.4 compatibility wrappers

1167 of 1361 branches covered (85.75%)

Branch coverage included in aggregate %.

2445 of 2723 relevant lines covered (89.79%)

2.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.93
/src/oemof/solph/components/_offset_converter.py
1
# -*- coding: utf-8 -
2

3
"""
4
OffsetConverter and associated individual constraints (blocks) and groupings.
5

6
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
7
SPDX-FileCopyrightText: Simon Hilpert
8
SPDX-FileCopyrightText: Cord Kaldemeyer
9
SPDX-FileCopyrightText: Patrik Schönfeldt
10
SPDX-FileCopyrightText: FranziPl
11
SPDX-FileCopyrightText: jnnr
12
SPDX-FileCopyrightText: Stephan Günther
13
SPDX-FileCopyrightText: FabianTU
14
SPDX-FileCopyrightText: Johannes Röder
15
SPDX-FileCopyrightText: Saeed Sayadi
16
SPDX-FileCopyrightText: Johannes Kochems
17
SPDX-FileCopyrightText: Francesco Witte
18

19
SPDX-License-Identifier: MIT
20

21
"""
22
from warnings import warn
3✔
23

24
from oemof.network import Node
3✔
25
from pyomo.core import BuildAction
3✔
26
from pyomo.core.base.block import ScalarBlock
3✔
27
from pyomo.environ import Constraint
3✔
28
from pyomo.environ import Set
3✔
29

30
from oemof.solph._plumbing import sequence
3✔
31

32

33
class OffsetConverter(Node):
3✔
34
    r"""An object with one input and multiple outputs and two coefficients
35
    per output to model part load behaviour.
36
    The output must contain a NonConvex object.
37

38
    Parameters
39
    ----------
40
    conversion_factors : dict, (:math:`m(t)`)
41
        Dict containing the respective bus as key and as value the parameter
42
        :math:`m(t)`. It represents the slope of a linear equation with
43
        respect to the `NonConvex` flow. The value can either be a scalar or a
44
        sequence with length of time horizon for simulation.
45

46
    normed_offsets : dict, (:math:`y_\text{0,normed}(t)`)
47
        Dict containing the respective bus as key and as value the parameter
48
        :math:`y_\text{0,normed}(t)`. It represents the y-intercept with respect
49
        to the `NonConvex` flow divided by the `nominal_value` of the
50
        `NonConvex` flow (this is for internal purposes). The value can either
51
        be a scalar or a sequence with length of time horizon for simulation.
52
    Notes
53
    -----
54

55
    :math:`m(t)` and :math:`y_\text{0,normed}(t)` can be calculated as follows:
56

57
    .. _OffsetConverterCoefficients-equations:
58

59
    .. math::
60

61
        m = \frac{(l_{max}/\eta_{max}-l_{min}/\eta_{min}}{l_{max}-l_{min}}
62

63
        y_\text{0,normed} = \frac{1}{\eta_{max}} - m
64

65
    Where :math:`l_{max}` and :math:`l_{min}` are the maximum and minimum
66
    partload share (e.g. 1.0 and 0.5) with reference to the `NonConvex` flow
67
    and :math:`\eta_{max}` and :math:`\eta_{min}` are the respective
68
    efficiencies/conversion factors at these partloads. Alternatively, you can
69
    use the inbuilt methods:
70

71
    - If the `NonConvex` flow is at an input of the component:
72
      :py:meth:`oemof.solph.components._offset_converter.slope_offset_from_nonconvex_input`,
73
    - If the `NonConvex` flow is at an output of the component:
74
      :py:meth:`oemof.solph.components._offset_converter.slope_offset_from_nonconvex_output`
75

76
    You can import these methods from the `oemof.solph.components` level:
77

78
    >>> from oemof.solph.components import slope_offset_from_nonconvex_input
79
    >>> from oemof.solph.components import slope_offset_from_nonconvex_output
80

81
    The sets, variables, constraints and objective parts are created
82
     * :py:class:`~oemof.solph.components._offset_converter.OffsetConverterBlock`
83

84
    Examples
85
    --------
86
    >>> from oemof import solph
87
    >>> bel = solph.buses.Bus(label='bel')
88
    >>> bth = solph.buses.Bus(label='bth')
89
    >>> l_nominal = 60
90
    >>> l_max = 1
91
    >>> l_min = 0.5
92
    >>> eta_max = 0.5
93
    >>> eta_min = 0.3
94
    >>> slope = (l_max / eta_max - l_min / eta_min) / (l_max - l_min)
95
    >>> offset = 1 / eta_max - slope
96

97
    Or use the provided method as explained in the previous section:
98

99
    >>> _slope, _offset = slope_offset_from_nonconvex_output(
100
    ...     l_max, l_min, eta_max, eta_min
101
    ... )
102
    >>> slope == _slope
103
    True
104
    >>> offset == _offset
105
    True
106

107
    >>> ostf = solph.components.OffsetConverter(
108
    ...    label='ostf',
109
    ...    inputs={bel: solph.flows.Flow()},
110
    ...    outputs={bth: solph.flows.Flow(
111
    ...         nominal_value=l_nominal, min=l_min, max=l_max,
112
    ...         nonconvex=solph.NonConvex())},
113
    ...    conversion_factors={bel: slope},
114
    ...    normed_offsets={bel: offset},
115
    ... )
116
    >>> type(ostf)
117
    <class 'oemof.solph.components._offset_converter.OffsetConverter'>
118

119
    The input required to operate at minimum load, can be computed from the
120
    slope and offset:
121

122
    >>> input_at_min = ostf.conversion_factors[bel][0] * l_min + ostf.normed_offsets[bel][0] * l_max
123
    >>> input_at_min * l_nominal
124
    100.0
125

126
    The same can be done for the input at nominal load:
127

128
    >>> input_at_max = l_max * (ostf.conversion_factors[bel][0] + ostf.normed_offsets[bel][0])
129
    >>> input_at_max * l_nominal
130
    120.0
131

132
    """  # noqa: E501
133

134
    def __init__(
3✔
135
        self,
136
        inputs,
137
        outputs,
138
        label=None,
139
        conversion_factors=None,
140
        normed_offsets=None,
141
        coefficients=None,
142
        custom_attributes=None,
143
    ):
144
        if custom_attributes is None:
3✔
145
            custom_attributes = {}
3✔
146

147
        super().__init__(
3✔
148
            inputs=inputs,
149
            outputs=outputs,
150
            label=label,
151
            custom_properties=custom_attributes,
152
        )
153

154
        # this part is used for the transition phase from the old
155
        # OffsetConverter API to the new one. It calcualtes the
156
        # conversion_factors and normed_offsets from the coefficients and the
157
        # outputs information on min and max.
158
        if (
3✔
159
            coefficients is not None
160
            and conversion_factors is None
161
            and normed_offsets is None
162
        ):
163
            normed_offsets, conversion_factors = (
3✔
164
                self.normed_offset_and_conversion_factors_from_coefficients(
165
                    coefficients
166
                )
167
            )
168

169
        elif coefficients is not None and (
3✔
170
            conversion_factors is not None or normed_offsets is not None
171
        ):
172
            msg = (
3✔
173
                "The deprecated argument `coefficients` cannot be used in "
174
                "combination with its replacements (`conversion_factors` and "
175
                "`normed_offsets`)."
176
            )
177
            raise TypeError(msg)
3✔
178

179
        _reference_flow = [v for v in self.inputs.values() if v.nonconvex]
3✔
180
        _reference_flow += [v for v in self.outputs.values() if v.nonconvex]
3✔
181
        if len(_reference_flow) != 1:
3✔
182
            raise ValueError(
3✔
183
                "Exactly one flow of the `OffsetConverter` must have the "
184
                "`NonConvex` attribute."
185
            )
186

187
        if _reference_flow[0] in self.inputs.values():
3✔
188
            self._reference_node_at_input = True
3✔
189
            self._reference_node = _reference_flow[0].input
3✔
190
        else:
191
            self._reference_node_at_input = False
3✔
192
            self._reference_node = _reference_flow[0].output
3✔
193

194
        _investment_node = [
3✔
195
            v.input for v in self.inputs.values() if v.investment
196
        ]
197
        _investment_node += [
3✔
198
            v.output for v in self.outputs.values() if v.investment
199
        ]
200

201
        if len(_investment_node) > 0:
3✔
202
            if (
3✔
203
                len(_investment_node) > 1
204
                or self._reference_node != _investment_node[0]
205
            ):
206
                raise TypeError(
3✔
207
                    "`Investment` attribute must be defined only for the "
208
                    "NonConvex flow!"
209
                )
210

211
        self._reference_flow = _reference_flow[0]
3✔
212

213
        if conversion_factors is None:
3!
214
            conversion_factors = {}
×
215

216
        if self._reference_node in conversion_factors:
3✔
217
            raise ValueError(
3✔
218
                "Conversion factors cannot be specified for the `NonConvex` "
219
                "flow."
220
            )
221

222
        self.conversion_factors = {
3✔
223
            k: sequence(v) for k, v in conversion_factors.items()
224
        }
225

226
        missing_conversion_factor_keys = (
3✔
227
            set(self.outputs) | set(self.inputs)
228
        ) - set(self.conversion_factors)
229

230
        for cf in missing_conversion_factor_keys:
3✔
231
            self.conversion_factors[cf] = sequence(1)
3✔
232

233
        if normed_offsets is None:
3!
234
            normed_offsets = {}
×
235

236
        if self._reference_node in normed_offsets:
3✔
237
            raise ValueError(
3✔
238
                "Normed offsets cannot be specified for the `NonConvex` flow."
239
            )
240

241
        self.normed_offsets = {
3✔
242
            k: sequence(v) for k, v in normed_offsets.items()
243
        }
244

245
        missing_normed_offsets_keys = (
3✔
246
            set(self.outputs) | set(self.inputs)
247
        ) - set(self.normed_offsets)
248

249
        for cf in missing_normed_offsets_keys:
3✔
250
            self.normed_offsets[cf] = sequence(0)
3✔
251

252
    def constraint_group(self):
3✔
253
        return OffsetConverterBlock
3✔
254

255
    def normed_offset_and_conversion_factors_from_coefficients(
3✔
256
        self, coefficients
257
    ):
258
        """
259
        Calculate slope and offset for new API from the old API coefficients.
260

261
        Parameters
262
        ----------
263
        coefficients : tuple
264
            tuple holding the coefficients (offset, slope) for the old style
265
            OffsetConverter.
266

267
        Returns
268
        -------
269
        tuple
270
            A tuple holding the slope and the offset for the new
271
            OffsetConverter API.
272
        """
273
        coefficients = tuple([sequence(i) for i in coefficients])
3✔
274
        if len(coefficients) != 2:
3✔
275
            raise ValueError(
3✔
276
                "Two coefficients or coefficient series have to be given."
277
            )
278

279
        input_bus = list(self.inputs.values())[0].input
3✔
280
        for flow in self.outputs.values():
3✔
281

282
            if flow.max.size is not None:
3!
283
                target_len = flow.max.size
×
284
            else:
285
                target_len = 1
3✔
286

287
            slope = []
3✔
288
            offset = []
3✔
289
            for i in range(target_len):
3✔
290
                eta_at_max = (
3✔
291
                    flow.max[i]
292
                    * coefficients[1][i]
293
                    / (flow.max[i] - coefficients[0][i])
294
                )
295
                eta_at_min = (
3✔
296
                    flow.min[i]
297
                    * coefficients[1][i]
298
                    / (flow.min[i] - coefficients[0][i])
299
                )
300

301
                c0, c1 = slope_offset_from_nonconvex_output(
3✔
302
                    flow.max[i], flow.min[i], eta_at_max, eta_at_min
303
                )
304
                slope.append(c0)
3✔
305
                offset.append(c1)
3✔
306

307
            if target_len == 1:
3!
308
                slope = slope[0]
3✔
309
                offset = offset[0]
3✔
310

311
            conversion_factors = {input_bus: slope}
3✔
312
            normed_offsets = {input_bus: offset}
3✔
313
            msg = (
3✔
314
                "The usage of coefficients is depricated, use "
315
                "conversion_factors and normed_offsets instead."
316
            )
317
            warn(msg, DeprecationWarning)
3✔
318

319
        return normed_offsets, conversion_factors
3✔
320

321
    def plot_partload(self, bus, tstep):
3✔
322
        """Create a matplotlib figure of the flow to nonconvex flow relation.
323

324
        Parameters
325
        ----------
326
        bus : oemof.solph.Bus
327
            Bus, to which the NOT-nonconvex input or output is connected to.
328
        tstep : int
329
            Timestep to generate the figure for.
330

331
        Returns
332
        -------
333
        tuple
334
            A tuple with the matplotlib figure and axes objects.
335
        """
336
        import matplotlib.pyplot as plt
×
337
        import numpy as np
×
338

339
        fig, ax = plt.subplots(2, sharex=True)
×
340

341
        slope = self.conversion_factors[bus][tstep]
×
342
        offset = self.normed_offsets[bus][tstep]
×
343

344
        min_load = self._reference_flow.min[tstep]
×
345
        max_load = self._reference_flow.max[tstep]
×
346

347
        infeasible_load = np.linspace(0, min_load)
×
348
        feasible_load = np.linspace(min_load, max_load)
×
349

350
        y_feasible = feasible_load * slope + offset
×
351
        y_infeasible = infeasible_load * slope + offset
×
352

353
        _ = ax[0].plot(feasible_load, y_feasible, label="operational range")
×
354
        color = _[0].get_color()
×
355
        ax[0].plot(infeasible_load, y_infeasible, "--", color=color)
×
356
        ax[0].scatter(
×
357
            [0, feasible_load[0], feasible_load[-1]],
358
            [y_infeasible[0], y_feasible[0], y_feasible[-1]],
359
            color=color,
360
        )
361
        ax[0].legend()
×
362

363
        ratio = y_feasible / feasible_load
×
364
        ax[1].plot(feasible_load, ratio)
×
365
        ax[1].scatter(
×
366
            [feasible_load[0], feasible_load[-1]],
367
            [ratio[0], ratio[-1]],
368
            color=color,
369
        )
370

371
        ax[0].set_ylabel(f"flow from/to bus '{bus.label}'")
×
372
        ax[1].set_ylabel("efficiency $\\frac{y}{x}$")
×
373
        ax[1].set_xlabel("nonconvex flow")
×
374

375
        _ = [(_.set_axisbelow(True), _.grid()) for _ in ax]
×
376
        plt.tight_layout()
×
377

378
        return fig, ax
×
379

380

381
class OffsetConverterBlock(ScalarBlock):
3✔
382
    r"""Block for the relation of nodes with type
383
    :class:`~oemof.solph.components._offset_converter.OffsetConverter`
384

385
    **The following constraints are created:**
386

387
    .. _OffsetConverter-equations:
388

389
    .. math::
390
        &
391
        P(p, t) = P_\text{ref}(p, t) \cdot m(t)
392
        + P_\text{nom,ref}(p) \cdot Y_\text{ref}(t) \cdot y_\text{0,normed}(t) \\
393

394

395
    The symbols used are defined as follows (with Variables (V) and Parameters (P)):
396

397
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
398
    | symbol                       | attribute                                                    | type | explanation                                                                 |
399
    +==============================+==============================================================+======+=============================================================================+
400
    | :math:`P(t)`                 | `flow[i,n,p,t]` or `flow[n,o,p,t]`                           | V    | **Non**-nonconvex flows at input or output                                  |
401
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
402
    | :math:`P_{in}(t)`            | `flow[i,n,p,t]` or `flow[n,o,p,t]`                           | V    | nonconvex flow of converter                                                 |
403
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
404
    | :math:`Y(t)`                 |                                                              | V    | Binary status variable of nonconvex flow                                    |
405
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
406
    | :math:`P_{nom}(t)`           |                                                              | V    | Nominal value (max. capacity) of the nonconvex flow                         |
407
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
408
    | :math:`m(t)`                 | `conversion_factors[i][n,t]` or `conversion_factors[o][n,t]` | P    | Linear coefficient 1 (slope) of a **Non**-nonconvex flows                   |
409
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
410
    | :math:`y_\text{0,normed}(t)` | `normed_offsets[i][n,t]` or `normed_offsets[o][n,t]`         | P    | Linear coefficient 0 (y-intersection)/P_{nom}(t) of **Non**-nonconvex flows |
411
    +------------------------------+--------------------------------------------------------------+------+-----------------------------------------------------------------------------+
412

413
    Note that :math:`P_{nom}(t) \cdot Y(t)` is merged into one variable,
414
    called `status_nominal[n, o, p, t]`.
415
    """  # noqa: E501
416

417
    CONSTRAINT_GROUP = True
3✔
418

419
    def __init__(self, *args, **kwargs):
3✔
420
        super().__init__(*args, **kwargs)
3✔
421

422
    def _create(self, group=None):
3✔
423
        """Creates the relation for the class:`OffsetConverter`.
424

425
        Parameters
426
        ----------
427
        group : list
428
            List of oemof.solph.experimental.OffsetConverter objects for
429
            which the relation of inputs and outputs is created
430
            e.g. group = [ostf1, ostf2, ostf3, ...]. The components inside
431
            the list need to hold an attribute `coefficients` of type dict
432
            containing the conversion factors for all inputs to outputs.
433
        """
434
        if group is None:
3!
435
            return None
×
436

437
        m = self.parent_block()
3✔
438

439
        self.OFFSETCONVERTERS = Set(initialize=[n for n in group])
3✔
440

441
        reference_node = {n: n._reference_node for n in group}
3✔
442
        reference_node_at_input = {
3✔
443
            n: n._reference_node_at_input for n in group
444
        }
445
        in_flows = {
3✔
446
            n: [i for i in n.inputs.keys() if i != n._reference_node]
447
            for n in group
448
        }
449
        out_flows = {
3✔
450
            n: [o for o in n.outputs.keys() if o != n._reference_node]
451
            for n in group
452
        }
453

454
        self.relation = Constraint(
3✔
455
            [
456
                (n, reference_node[n], f, t)
457
                for t in m.TIMESTEPS
458
                for n in group
459
                for f in in_flows[n] + out_flows[n]
460
            ],
461
            noruleinit=True,
462
        )
463

464
        def _relation_rule(block):
3✔
465
            """Link binary input and output flow to component outflow."""
466
            for t in m.TIMESTEPS:
3✔
467
                for n in group:
3✔
468

469
                    if reference_node_at_input[n]:
3✔
470
                        ref_flow = m.flow[reference_node[n], n, t]
3✔
471
                        status_nominal_idx = reference_node[n], n, t
3✔
472
                    else:
473
                        ref_flow = m.flow[n, reference_node[n], t]
3✔
474
                        status_nominal_idx = n, reference_node[n], t
3✔
475

476
                    try:
3✔
477
                        ref_status_nominal = (
3✔
478
                            m.InvestNonConvexFlowBlock.status_nominal[
479
                                status_nominal_idx
480
                            ]
481
                        )
482
                    except (AttributeError, KeyError):
3✔
483
                        ref_status_nominal = (
3✔
484
                            m.NonConvexFlowBlock.status_nominal[
485
                                status_nominal_idx
486
                            ]
487
                        )
488

489
                    for f in in_flows[n] + out_flows[n]:
3✔
490
                        rhs = 0
3✔
491
                        if f in in_flows[n]:
3✔
492
                            rhs += m.flow[f, n, t]
3✔
493
                        else:
494
                            rhs += m.flow[n, f, t]
3✔
495

496
                        lhs = 0
3✔
497
                        lhs += ref_flow * n.conversion_factors[f][t]
3✔
498
                        lhs += ref_status_nominal * n.normed_offsets[f][t]
3✔
499
                        block.relation.add(
3✔
500
                            (n, reference_node[n], f, t), (lhs == rhs)
501
                        )
502

503
        self.relation_build = BuildAction(rule=_relation_rule)
3✔
504

505

506
def slope_offset_from_nonconvex_input(
3✔
507
    max_load, min_load, eta_at_max, eta_at_min
508
):
509
    r"""Calculate the slope and the offset with max and min given for input
510

511
    The reference is the input flow here. That means, the `NonConvex` flow
512
    is specified at one of the input flows. The `max_load` and the `min_load`
513
    are the `max` and the `min` specifications for the `NonConvex` flow.
514
    `eta_at_max` and `eta_at_min` are the efficiency values of a different
515
    flow, e.g. an output, with respect to the `max_load` and `min_load`
516
    operation points.
517

518
    .. math::
519

520
        \text{slope} =
521
        \frac{
522
            \text{max} \cdot \eta_\text{at max}
523
            - \text{min} \cdot \eta_\text{at min}
524
        }{\text{max} - \text{min}}\\
525

526
        \text{offset} = \eta_\text{at,max} - \text{slope}
527

528
    Parameters
529
    ----------
530
    max_load : float
531
        Maximum load value, e.g. 1
532
    min_load : float
533
        Minimum load value, e.g. 0.5
534
    eta_at_max : float
535
        Efficiency at maximum load.
536
    eta_at_min : float
537
        Efficiency at minimum load.
538

539
    Returns
540
    -------
541
    tuple
542
        slope and offset
543

544
    Example
545
    -------
546
    >>> from oemof import solph
547
    >>> max_load = 1
548
    >>> min_load = 0.5
549
    >>> eta_at_min = 0.4
550
    >>> eta_at_max = 0.3
551

552
    With the input load being at 100 %, in this example, the efficiency should
553
    be 30 %. With the input load being at 50 %, it should be 40 %. We can
554
    calcualte slope and the offset which is normed to the nominal value of
555
    the referenced flow (in this case the input flow) always.
556

557
    >>> slope, offset = solph.components.slope_offset_from_nonconvex_input(
558
    ...     max_load, min_load, eta_at_max, eta_at_min
559
    ... )
560
    >>> input_flow = 10
561
    >>> input_flow_nominal = 10
562
    >>> output_flow = slope * input_flow + offset * input_flow_nominal
563

564
    We can then calculate with the `OffsetConverter` input output relation,
565
    what the resulting efficiency is. At max operating conditions it should be
566
    identical to the efficiency we put in initially. Analogously, we apply this
567
    to the minimal load point.
568

569
    >>> round(output_flow / input_flow, 3) == eta_at_max
570
    True
571
    >>> input_flow = 5
572
    >>> output_flow = slope * input_flow + offset * input_flow_nominal
573
    >>> round(output_flow / input_flow, 3) == eta_at_min
574
    True
575
    """
576
    slope = (max_load * eta_at_max - min_load * eta_at_min) / (
3✔
577
        max_load - min_load
578
    )
579
    offset = eta_at_max - slope
3✔
580
    return slope, offset
3✔
581

582

583
def slope_offset_from_nonconvex_output(
3✔
584
    max_load, min_load, eta_at_max, eta_at_min
585
):
586
    r"""Calculate the slope and the offset with max and min given for output.
587

588
    The reference is the output flow here. That means, the `NonConvex` flow
589
    is specified at one of the output flows. The `max_load` and the `min_load`
590
    are the `max` and the `min` specifications for the `NonConvex` flow.
591
    `eta_at_max` and `eta_at_min` are the efficiency values of a different
592
    flow, e.g. an input, with respect to the `max_load` and `min_load`
593
    operation points.
594

595
    .. math::
596

597
        \text{slope} =
598
        \frac{
599
            \frac{\text{max}}{\eta_\text{at max}}
600
            - \frac{\text{min}}{\eta_\text{at min}}
601
        }{\text{max} - \text{min}}\\
602

603
        \text{offset} = \frac{1}{\eta_\text{at,max}} - \text{slope}
604

605
    Parameters
606
    ----------
607
    max_load : float
608
        Maximum load value, e.g. 1
609
    min_load : float
610
        Minimum load value, e.g. 0.5
611
    eta_at_max : float
612
        Efficiency at maximum load.
613
    eta_at_min : float
614
        Efficiency at minimum load.
615

616
    Returns
617
    -------
618
    tuple
619
        slope and offset
620

621
    Example
622
    -------
623
    >>> from oemof import solph
624
    >>> max_load = 1
625
    >>> min_load = 0.5
626
    >>> eta_at_min = 0.7
627
    >>> eta_at_max = 0.8
628

629
    With the output load being at 100 %, in this example, the efficiency should
630
    be 80 %. With the input load being at 50 %, it should be 70 %. We can
631
    calcualte slope and the offset, which is normed to the nominal value of
632
    the referenced flow (in this case the output flow) always.
633

634
    >>> slope, offset = solph.components.slope_offset_from_nonconvex_output(
635
    ...     max_load, min_load, eta_at_max, eta_at_min
636
    ... )
637
    >>> output_flow = 10
638
    >>> output_flow_nominal = 10
639
    >>> input_flow = slope * output_flow + offset * output_flow_nominal
640

641
    We can then calculate with the `OffsetConverter` input output relation,
642
    what the resulting efficiency is. At max operating conditions it should be
643
    identical to the efficiency we put in initially. Analogously, we apply this
644
    to the minimal load point.
645

646
    >>> round(output_flow / input_flow, 3) == eta_at_max
647
    True
648
    >>> output_flow = 5
649
    >>> input_flow = slope * output_flow + offset * output_flow_nominal
650
    >>> round(output_flow / input_flow, 3) == eta_at_min
651
    True
652
    """
653
    slope = (max_load / eta_at_max - min_load / eta_at_min) / (
3✔
654
        max_load - min_load
655
    )
656
    offset = 1 / eta_at_max - slope
3✔
657
    return slope, offset
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc