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

romainsacchi / carculator / 10215603232

02 Aug 2024 12:30PM UTC coverage: 88.94% (+0.2%) from 88.785%
10215603232

push

github

romainsacchi
Verison bump

1 of 1 new or added line in 1 file covered. (100.0%)

14 existing lines in 1 file now uncovered.

193 of 217 relevant lines covered (88.94%)

0.89 hits per line

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

86.93
/carculator/model.py
1
from itertools import product
1✔
2

3
import numpy as np
1✔
4
import yaml
1✔
5
from carculator_utils.energy_consumption import EnergyConsumptionModel
1✔
6
from carculator_utils.model import VehicleModel
1✔
7

8
from . import DATA_DIR
1✔
9

10

11
class CarModel(VehicleModel):
1✔
12
    """
13
    This class represents the entirety of the vehicles considered, with useful attributes, such as an array that stores
14
    all the vehicles parameters.
15

16
    :ivar array: multi-dimensional numpy-like array that contains parameters' value(s)
17
    :ivar cycle: name of a driving cycle, or custom driving cycle
18
    :ivar gradient: series of gradients, for each second of the driving cycle
19
    :ivar energy_storage: dictionary with selection of battery chemistry for each powertrain
20

21
    """
22

23
    def set_all(self):
1✔
24
        """
25
        This method runs a series of other methods to obtain the tank-to-wheel energy requirement, efficiency
26
        of the car, costs, etc.
27

28
        :meth:`set_component_masses()`, :meth:`set_car_masses()` and :meth:`set_power_parameters()` are interdependent.
29
        `powertrain_mass` depends on `power`, `curb_mass` is affected by changes in `powertrain_mass`,
30
        `combustion engine mass` and `electric engine mass`, and `power` is a function of `curb_mass`.
31
        The current solution is to loop through the methods until the increment in driving mass is
32
        inferior to 0.1%.
33

34
        :param drop_hybrids: boolean. True by default. If False, the underlying vehicles used to build plugin-hybrid
35
                vehicles remain present in the array.
36
        :param electric_utility_factor: array. If an array is passed, its values are used to override the
37
                electric utility factor for plugin hybrid vehicles. If not, this factor is calculated using a relation
38
                described in `set_electric_utility_factor()`
39

40
        :returns: Does not return anything. Modifies ``self.array`` in place.
41

42
        """
43

44
        self.ecm = EnergyConsumptionModel(
1✔
45
            vehicle_type="car",
46
            vehicle_size=list(self.array.coords["size"].values),
47
            powertrains=list(self.array.coords["powertrain"].values),
48
            cycle=self.cycle,
49
            gradient=self.gradient,
50
            country=self.country,
51
        )
52

53
        diff = 1.0
1✔
54

55
        while diff > 0.00001:
1✔
56
            old_driving_mass = self["driving mass"].sum().values
1✔
57
            self.set_vehicle_mass()
1✔
58
            self.set_power_parameters()
1✔
59
            self.set_component_masses()
1✔
60
            self.set_auxiliaries()
1✔
61
            self.set_power_battery_properties()
1✔
62
            self.set_battery_properties()
1✔
63
            self.set_energy_stored_properties()
1✔
64
            self.set_recuperation()
1✔
65

66
            if "FCEV" in self.array.powertrain.values:
1✔
67
                self.set_fuel_cell_power()
1✔
68
                self.set_fuel_cell_mass()
1✔
69

70
            # if user-provided values are passed,
71
            # they override the default values
72
            if "capacity" in self.energy_storage:
1✔
73
                self.override_battery_capacity()
1✔
74

75
            diff = (self["driving mass"].sum().values - old_driving_mass) / self[
1✔
76
                "driving mass"
77
            ].sum()
78

79
        self.calculate_ttw_energy()
1✔
80
        self.set_ttw_efficiency()
1✔
81

82
        self.set_range()
1✔
83

84
        if self.target_range:
1✔
85
            self.override_range()
1✔
86

87
        self.set_share_recuperated_energy()
1✔
88
        self.set_battery_fuel_cell_replacements()
1✔
89
        self.adjust_cost()
1✔
90

91
        self.set_electric_utility_factor()
1✔
92
        self.set_electricity_consumption()
1✔
93
        self.set_costs()
1✔
94
        self.set_hot_emissions()
1✔
95
        self.set_particulates_emission()
1✔
96
        self.set_noise_emissions()
1✔
97
        self.set_vehicle_mass()
1✔
98
        self.create_PHEV()
1✔
99
        if self.drop_hybrids:
1✔
100
            self.drop_hybrid()
1✔
101

102
        self.remove_energy_consumption_from_unavailable_vehicles()
1✔
103

104
    def set_battery_chemistry(self):
1✔
105
        # override default values for batteries
106
        # if provided by the user
107
        if "electric" not in self.energy_storage:
1✔
108
            self.energy_storage["electric"] = {}
1✔
109

110
        for x in product(
1✔
111
            self.array.coords["powertrain"].values,
112
            self.array.coords["size"].values,
113
            self.array.year.values,
114
        ):
115
            if x not in self.energy_storage["electric"]:
1✔
116
                self.energy_storage["electric"][x] = "NMC-622"
1✔
117

118
        if "origin" not in self.energy_storage:
1✔
119
            self.energy_storage.update({"origin": "CN"})
1✔
120

121
    def adjust_cost(self) -> None:
1✔
122
        """
123
        This method adjusts costs of energy storage over time, to correct for the overly optimistic linear
124
        interpolation between years.
125

126
        """
127

128
        n_iterations = self.array.shape[-1]
1✔
129
        n_year = len(self.array.year.values)
1✔
130

131
        # If uncertainty is not considered, the cost factor equals 1.
132
        # Otherwise, a variability of +/-30% is added.
133

134
        if n_iterations == 1:
1✔
135
            cost_factor = 1
1✔
136
        else:
UNCOV
137
            if "reference" in self.array.value.values.tolist():
×
UNCOV
138
                cost_factor = np.ones((n_iterations, 1))
×
139
            else:
140
                cost_factor = np.random.triangular(0.7, 1, 1.3, (n_iterations, 1))
×
141

142
        # Correction of hydrogen tank cost, per kg
143
        # Correction of fuel cell stack cost, per kW
144
        if "FCEV" in self.array.powertrain:
1✔
145
            self.array.loc[
1✔
146
                dict(powertrain="FCEV", parameter="fuel tank cost per kg")
147
            ] = np.reshape(
148
                (1.078e58 * np.exp(-6.32e-2 * self.array.year.values) + 3.43e2)
149
                * cost_factor,
150
                (1, n_year, n_iterations),
151
            )
152

153
            self.array.loc[
1✔
154
                dict(powertrain="FCEV", parameter="fuel tank cost per kg")
155
            ] = np.reshape(
156
                (3.15e66 * np.exp(-7.35e-2 * self.array.year.values) + 2.39e1)
157
                * cost_factor,
158
                (1, n_year, n_iterations),
159
            )
160

161
        # Correction of energy battery system cost, per kWh
162
        list_batt = [
1✔
163
            i
164
            for i in ["BEV", "PHEV-e", "PHEV-c-p", "PHEV-c-d"]
165
            if i in self.array.powertrain
166
        ]
167
        if len(list_batt) > 0:
1✔
168
            self.array.loc[
1✔
169
                dict(powertrain=list_batt, parameter="energy battery cost per kWh")
170
            ] = np.reshape(
171
                (2.75e86 * np.exp(-9.61e-2 * self.array.year.values) + 5.059e1)
172
                * cost_factor,
173
                (1, 1, n_year, n_iterations),
174
            )
175

176
        # Correction of power battery system cost, per kW
177
        list_pwt = [
1✔
178
            i
179
            for i in [
180
                "ICEV-p",
181
                "ICEV-d",
182
                "ICEV-g",
183
                "PHEV-c-p",
184
                "PHEV-c-d",
185
                "FCEV",
186
                "HEV-p",
187
                "HEV-d",
188
            ]
189
            if i in self.array.powertrain
190
        ]
191

192
        if len(list_pwt) > 0:
1✔
193
            self.array.loc[
1✔
194
                dict(powertrain=list_pwt, parameter="power battery cost per kW")
195
            ] = np.reshape(
196
                (8.337e40 * np.exp(-4.49e-2 * self.array.year.values) + 11.17)
197
                * cost_factor,
198
                (1, 1, n_year, n_iterations),
199
            )
200

201
        # Correction of combustion powertrain cost for ICEV-g
202
        if "ICEV-g" in self.array.powertrain:
1✔
203
            self.array.loc[
1✔
204
                dict(powertrain="ICEV-g", parameter="combustion powertrain cost per kW")
205
            ] = np.clip(
206
                np.reshape(
207
                    (5.92e160 * np.exp(-0.1819 * self.array.year.values) + 26.76)
208
                    * cost_factor,
209
                    (1, n_year, n_iterations),
210
                ),
211
                None,
212
                100,
213
            )
214

215
    def calculate_ttw_energy(self) -> None:
1✔
216
        """
217
        This method calculates the energy required to operate auxiliary services as well
218
        as to move the car. The sum is stored under the parameter label "TtW energy" in :attr:`self.array`.
219

220
        """
221

222
        self.energy = self.ecm.motive_energy_per_km(
1✔
223
            driving_mass=self["driving mass"],
224
            rr_coef=self["rolling resistance coefficient"],
225
            drag_coef=self["aerodynamic drag coefficient"],
226
            frontal_area=self["frontal area"],
227
            electric_motor_power=self["electric power"],
228
            engine_power=self["power"],
229
            recuperation_efficiency=self["recuperation efficiency"],
230
            aux_power=self["auxiliary power demand"],
231
            battery_charge_eff=self["battery charge efficiency"],
232
            battery_discharge_eff=self["battery discharge efficiency"],
233
            fuel_cell_system_efficiency=self["fuel cell system efficiency"],
234
        )
235

236
        self.energy = self.energy.assign_coords(
1✔
237
            {
238
                "powertrain": self.array.powertrain,
239
                "year": self.array.year,
240
                "size": self.array.coords["size"],
241
                "value": self.array.coords["value"],
242
            }
243
        )
244

245
        if self.energy_consumption:
1✔
246
            self.override_ttw_energy()
1✔
247

248
        distance = self.energy.sel(parameter="velocity").sum(dim="second") / 1000
1✔
249

250
        self["transmission efficiency"] = (
1✔
251
            np.ma.array(
252
                self.energy.loc[dict(parameter="transmission efficiency")],
253
                mask=self.energy.loc[dict(parameter="power load")] == 0,
254
            )
255
            .mean(axis=0)
256
            .T
257
        )
258

259
        self["engine efficiency"] = (
1✔
260
            (
261
                (self.energy.sel(parameter="motive energy at wheels").sum(dim="second"))
262
                / (self.energy.sel(parameter="motive energy").sum(dim="second"))
263
            ).T
264
            / self["transmission efficiency"]
265
            / np.where(
266
                self["fuel cell system efficiency"] > 0,
267
                self["fuel cell system efficiency"],
268
                1,
269
            )
270
        )
271

272
        _o = lambda x: np.where((x == 0) | (x == np.nan), 1, x)
1✔
273

274
        if self.engine_efficiency is not None:
1✔
UNCOV
275
            print("Engine efficiency is being overridden.")
×
UNCOV
276
            for key, val in self.engine_efficiency.items():
×
277
                pwt, size, year = key
×
278
                if (
×
279
                    (val is not None)
280
                    & (pwt in self.array.powertrain.values)
281
                    & (year in self.array.year.values)
282
                    & (size in self.array["size"].values)
283
                ):
UNCOV
284
                    self.array.loc[
×
285
                        dict(
286
                            powertrain=pwt,
287
                            size=size,
288
                            year=year,
289
                            parameter="engine efficiency",
290
                        )
291
                    ] = float(val)
292

UNCOV
293
                    self.energy.loc[
×
294
                        dict(
295
                            powertrain=pwt,
296
                            size=size,
297
                            year=year,
298
                            parameter="engine efficiency",
299
                        )
300
                    ] = float(val) * np.where(
301
                        self.energy.loc[
302
                            dict(
303
                                parameter="power load",
304
                                powertrain=pwt,
305
                                size=size,
306
                                year=year,
307
                            )
308
                        ]
309
                        == 0,
310
                        0,
311
                        1,
312
                    )
313

UNCOV
314
                    self.energy.loc[
×
315
                        dict(
316
                            powertrain=pwt,
317
                            size=size,
318
                            year=year,
319
                            parameter="motive energy",
320
                        )
321
                    ] = self.energy.loc[
322
                        dict(
323
                            powertrain=pwt,
324
                            size=size,
325
                            year=year,
326
                            parameter="motive energy at wheels",
327
                        )
328
                    ] / (
329
                        _o(
330
                            self.energy.loc[
331
                                dict(
332
                                    powertrain=pwt,
333
                                    size=size,
334
                                    year=year,
335
                                    parameter="engine efficiency",
336
                                )
337
                            ]
338
                        )
339
                        * _o(
340
                            self.energy.loc[
341
                                dict(
342
                                    powertrain=pwt,
343
                                    size=size,
344
                                    year=year,
345
                                    parameter="transmission efficiency",
346
                                )
347
                            ]
348
                        )
349
                        * _o(
350
                            self.array.loc[
351
                                dict(
352
                                    powertrain=pwt,
353
                                    size=size,
354
                                    year=year,
355
                                    parameter="fuel cell system efficiency",
356
                                )
357
                            ]
358
                        )
359
                    )
360

361
        if self.transmission_efficiency is not None:
1✔
UNCOV
362
            print("Transmission efficiency is being overridden.")
×
UNCOV
363
            for key, val in self.transmission_efficiency.items():
×
364
                pwt, size, year = key
×
365

366
                if (
×
367
                    (val is not None)
368
                    & (pwt in self.array.powertrain.values)
369
                    & (year in self.array.year.values)
370
                    & (size in self.array["size"].values)
371
                ):
UNCOV
372
                    self.array.loc[
×
373
                        dict(
374
                            powertrain=pwt,
375
                            size=size,
376
                            year=year,
377
                            parameter="transmission efficiency",
378
                        )
379
                    ] = float(val)
380

UNCOV
381
                    self.energy.loc[
×
382
                        dict(
383
                            powertrain=pwt,
384
                            size=size,
385
                            year=year,
386
                            parameter="transmission efficiency",
387
                        )
388
                    ] = float(val) * np.where(
389
                        self.energy.loc[
390
                            dict(
391
                                parameter="power load",
392
                                powertrain=pwt,
393
                                size=size,
394
                                year=year,
395
                            )
396
                        ]
397
                        == 0,
398
                        0,
399
                        1,
400
                    )
401

UNCOV
402
                    self.energy.loc[
×
403
                        dict(
404
                            powertrain=pwt,
405
                            size=size,
406
                            year=year,
407
                            parameter="motive energy",
408
                        )
409
                    ] = self.energy.loc[
410
                        dict(
411
                            powertrain=pwt,
412
                            size=size,
413
                            year=year,
414
                            parameter="motive energy at wheels",
415
                        )
416
                    ] / (
417
                        _o(
418
                            self.energy.loc[
419
                                dict(
420
                                    powertrain=pwt,
421
                                    size=size,
422
                                    year=year,
423
                                    parameter="engine efficiency",
424
                                )
425
                            ]
426
                        )
427
                        * _o(
428
                            self.energy.loc[
429
                                dict(
430
                                    powertrain=pwt,
431
                                    size=size,
432
                                    year=year,
433
                                    parameter="transmission efficiency",
434
                                )
435
                            ]
436
                        )
437
                        * _o(
438
                            self.array.loc[
439
                                dict(
440
                                    powertrain=pwt,
441
                                    size=size,
442
                                    year=year,
443
                                    parameter="fuel cell system efficiency",
444
                                )
445
                            ]
446
                        )
447
                    )
448

449
        self["TtW energy"] = (
1✔
450
            self.energy.sel(
451
                parameter=[
452
                    "motive energy",
453
                    "auxiliary energy",
454
                ]
455
            ).sum(dim=["second", "parameter"])
456
            / distance
457
        ).T
458

459
        # saved_TtW_energy_by_recuperation = recuperated energy * electric motor efficiency * electric transmission efficency / (engine efficiency * transmission efficiency)
460

461
        self["TtW energy"] += (
1✔
462
            (
463
                self.energy.sel(parameter="recuperated energy").sum(dim="second")
464
                / distance
465
            ).T
466
            * self.array.sel(parameter="engine efficiency")
467
            * self.array.sel(parameter="transmission efficiency")
468
            / (
469
                self["engine efficiency"]
470
                * self["transmission efficiency"]
471
                * np.where(
472
                    self["fuel cell system efficiency"] == 0,
473
                    1,
474
                    self["fuel cell system efficiency"],
475
                )
476
            )
477
        )
478

479
        self["TtW energy, combustion mode"] = self["TtW energy"] * (
1✔
480
            self["combustion power share"] > 0
481
        )
482
        self["TtW energy, electric mode"] = self["TtW energy"] * (
1✔
483
            self["combustion power share"] == 0
484
        )
485

486
        self["auxiliary energy"] = (
1✔
487
            self.energy.sel(parameter="auxiliary energy").sum(dim="second") / distance
488
        ).T
489

490
    def set_vehicle_mass(self) -> None:
1✔
491
        """
492
        Define ``curb mass``, ``driving mass``, and ``total cargo mass``.
493

494
            * `curb mass <https://en.wikipedia.org/wiki/Curb_weight>`__ is the mass of the vehicle and fuel, without people or cargo.
495
            * ``total cargo mass`` is the mass of the cargo and passengers.
496
            * ``driving mass`` is the ``curb mass`` plus ``total cargo mass``.
497

498
        .. note::
499
            driving mass = total cargo mass + driving mass
500

501
        """
502

503
        self["curb mass"] = self["glider base mass"] * (1 - self["lightweighting"])
1✔
504

505
        curb_mass_includes = [
1✔
506
            "fuel mass",
507
            "charger mass",
508
            "converter mass",
509
            "inverter mass",
510
            "power distribution unit mass",
511
            # Updates with set_components_mass
512
            "combustion engine mass",
513
            # Updates with set_components_mass
514
            "electric engine mass",
515
            # Updates with set_components_mass
516
            "powertrain mass",
517
            "fuel cell stack mass",
518
            "fuel cell ancillary BoP mass",
519
            "fuel cell essential BoP mass",
520
            "battery cell mass",
521
            "battery BoP mass",
522
            "fuel tank mass",
523
        ]
524

525
        self["curb mass"] += self[curb_mass_includes].sum(axis=2)
1✔
526

527
        if self.target_mass:
1✔
528
            self.override_vehicle_mass()
1✔
529

530
        self["total cargo mass"] = (
1✔
531
            self["average passengers"] * self["average passenger mass"]
532
            + self["cargo mass"]
533
        )
534
        self["driving mass"] = self["curb mass"] + self["total cargo mass"]
1✔
535

536
    def set_electric_utility_factor(self) -> None:
1✔
537
        """Set the electric utility factor according to a sampled values in Germany (ICTT 2022)
538
        https://theicct.org/wp-content/uploads/2022/06/real-world-phev-use-jun22-1.pdf
539

540
        Real-world range in simulation 20 km 30 km 40 km 50 km 60 km 70 km 80 km
541
        Observed UF for Germany (Sample-size weighted regression ± 2 standard errors)
542
        Observed UF private (in %) 30±2 41±2 50±3 58±3 65±3 71±3 75±3
543

544
        which correlated the share of km driven in electric-mode to
545
        the capacity of the battery
546
        (the range that can be driven in battery-depleting mode).
547

548
        The argument `uf` is used to override this relation, if needed.
549
        `uf` must be a ratio between 0 and .75, for each."""
550

551
        if "PHEV-e" in self.array.coords["powertrain"].values:
1✔
552
            if self.electric_utility_factor is None:
1✔
553
                self.array.loc[
1✔
554
                    dict(powertrain="PHEV-e", parameter="electric utility factor")
555
                ] = np.clip(
556
                    np.interp(
557
                        self.array.loc[dict(powertrain="PHEV-e", parameter="range")],
558
                        [0, 20, 30, 40, 50, 60, 70, 80, 100, 120, 200],
559
                        [0, 0.13, 0.18, 0.23, 0.28, 0.30, 0.35, 0.40, 0.45, 0.5, 0.75],
560
                    ),
561
                    0,
562
                    0.75,
563
                )
564
            else:
565
                for key, val in self.electric_utility_factor.items():
×
UNCOV
566
                    if (
×
567
                        "PHEV-e" in self.array.powertrain.values
568
                        and key in self.array.year.values
569
                    ):
UNCOV
570
                        self.array.loc[
×
571
                            dict(
572
                                powertrain="PHEV-e",
573
                                parameter="electric utility factor",
574
                                year=key,
575
                            )
576
                        ] = val
577

578
    def set_costs(self) -> None:
1✔
579
        """
580
        Calculate the different cost types.
581
        :return:
582
        """
583
        self["glider cost"] = (
1✔
584
            self["glider base mass"] * self["glider cost slope"]
585
            + self["glider cost intercept"]
586
        )
587
        self["lightweighting cost"] = (
1✔
588
            self["glider base mass"]
589
            * self["lightweighting"]
590
            * self["glider lightweighting cost per kg"]
591
        )
592
        self["electric powertrain cost"] = (
1✔
593
            self["electric powertrain cost per kW"] * self["electric power"]
594
        )
595
        self["combustion powertrain cost"] = (
1✔
596
            self["combustion power"] * self["combustion powertrain cost per kW"]
597
        )
598
        self["fuel cell cost"] = self["fuel cell power"] * self["fuel cell cost per kW"]
1✔
599
        self["power battery cost"] = (
1✔
600
            self["battery power"] * self["power battery cost per kW"]
601
        )
602
        self["energy battery cost"] = (
1✔
603
            self["energy battery cost per kWh"] * self["electric energy stored"]
604
        )
605
        self["fuel tank cost"] = self["fuel tank cost per kg"] * self["fuel mass"]
1✔
606
        # Per km
607
        self["energy cost"] = self["energy cost per kWh"] * self["TtW energy"] / 3600
1✔
608

609
        # For battery, need to divide cost of electricity
610
        # at battery by efficiency of charging
611
        # to get costs at the "wall socket".
612

613
        _ = lambda x: np.where(x == 0, 1, x)
1✔
614
        self["energy cost"] /= _(self["battery charge efficiency"])
1✔
615

616
        self["component replacement cost"] = (
1✔
617
            self["energy battery cost"] * self["battery lifetime replacements"]
618
            + self["fuel cell cost"] * self["fuel cell lifetime replacements"]
619
        )
620

621
        with open(DATA_DIR / "purchase_cost_params.yaml", "r") as stream:
1✔
622
            to_markup = yaml.safe_load(stream)["markup"]
1✔
623

624
        self[to_markup] *= self["markup factor"]
1✔
625

626
        # calculate costs per km:
627
        self["lifetime"] = self["lifetime kilometers"] / self["kilometers per year"]
1✔
628

629
        with open(DATA_DIR / "purchase_cost_params.yaml", "r") as stream:
1✔
630
            purchase_cost_params = yaml.safe_load(stream)["purchase"]
1✔
631

632
        self["purchase cost"] = self[purchase_cost_params].sum(axis=2)
1✔
633
        # per km
634
        amortisation_factor = self["interest rate"] + (
1✔
635
            self["interest rate"]
636
            / (
637
                (np.array(1) + self["interest rate"]) ** self["lifetime kilometers"]
638
                - np.array(1)
639
            )
640
        )
641
        self["amortised purchase cost"] = (
1✔
642
            self["purchase cost"] * amortisation_factor / self["kilometers per year"]
643
        )
644

645
        # per km
646
        self["maintenance cost"] = (
1✔
647
            self["maintenance cost per glider cost"]
648
            * self["glider cost"]
649
            / self["kilometers per year"]
650
        )
651

652
        # simple assumption that component replacement
653
        # occurs at half of life.
654
        self["amortised component replacement cost"] = (
1✔
655
            (
656
                self["component replacement cost"]
657
                * (
658
                    (np.array(1) - self["interest rate"]) ** self["lifetime kilometers"]
659
                    / 2
660
                )
661
            )
662
            * amortisation_factor
663
            / self["kilometers per year"]
664
        )
665

666
        self["total cost per km"] = (
1✔
667
            self["energy cost"]
668
            + self["amortised purchase cost"]
669
            + self["maintenance cost"]
670
            + self["amortised component replacement cost"]
671
        )
672

673
    def remove_energy_consumption_from_unavailable_vehicles(self):
1✔
674
        """
675
        This method sets the energy consumption of vehicles that are not available to zero.
676
        """
677

678
        # we flag cars that have a range inferior to 100 km
679
        # and also BEVs, PHEVs and FCEVs from before 2013
680
        self["TtW energy"] = np.where((self["range"] < 100), 0, self["TtW energy"])
1✔
681

682
        pwts = [
1✔
683
            pt
684
            for pt in [
685
                "BEV",
686
                "PHEV-e",
687
                "PHEV-c-p",
688
                "PHEV-c-d",
689
                "FCEV",
690
                "PHEV-p",
691
                "PHEV-d",
692
                "HEV-d",
693
                "HEV-p",
694
            ]
695
            if pt in self.array.coords["powertrain"].values
696
        ]
697

698
        years = [y for y in self.array.year.values if y < 2013]
1✔
699

700
        if years:
1✔
701
            self.array.loc[
1✔
702
                dict(
703
                    parameter="TtW energy",
704
                    powertrain=pwts,
705
                    year=years,
706
                )
707
            ] = 0
708

709
        # and also Micro cars other than BEVs
710
        if "Micro" in self.array.coords["size"].values:
1✔
711
            self.array.loc[
1✔
712
                dict(
713
                    parameter="TtW energy",
714
                    powertrain=[
715
                        pt
716
                        for pt in [
717
                            "PHEV-e",
718
                            "PHEV-c-p",
719
                            "PHEV-c-d",
720
                            "FCEV",
721
                            "PHEV-p",
722
                            "PHEV-d",
723
                            "HEV-d",
724
                            "HEV-p",
725
                            "ICEV-p",
726
                            "ICEV-d",
727
                            "ICEV-g",
728
                        ]
729
                        if pt in self.array.coords["powertrain"].values
730
                    ],
731
                    size="Micro",
732
                )
733
            ] = 0
734

735
            # replace Nans with zeros
736
            self.array.loc[dict(size="Micro")] = self.array.loc[
1✔
737
                dict(size="Micro")
738
            ].fillna(0)
739

740
        if "BEV" in self.array.coords["powertrain"].values:
1✔
741
            # set the `TtW energy` of BEV vehicles before 2010 to zero
742
            self.array.loc[
1✔
743
                dict(
744
                    powertrain="BEV",
745
                    year=slice(None, 2010),
746
                    parameter="TtW energy",
747
                )
748
            ] = 0
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