Conditions | 8 |
Total Lines | 543 |
Code Lines | 315 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
1 | # -*- coding: utf-8 -*- |
||
72 | def main(optimize=True): |
||
73 | ########################################################################## |
||
74 | # Initialize the energy system and calculate necessary parameters |
||
75 | ########################################################################## |
||
76 | |||
77 | # Start time for calculating the total elapsed time. |
||
78 | start_simulation_time = time.time() |
||
79 | |||
80 | start = "2022-01-01" |
||
81 | |||
82 | # The maximum number of days depends on the given csv file. |
||
83 | n_days = 5 |
||
84 | n_days_in_year = 365 |
||
85 | |||
86 | # Create date and time objects. |
||
87 | start_date_obj = datetime.strptime(start, "%Y-%m-%d") |
||
88 | start_date = start_date_obj.date() |
||
89 | start_time = start_date_obj.time() |
||
90 | start_datetime = datetime.combine( |
||
91 | start_date_obj.date(), start_date_obj.time() |
||
92 | ) |
||
93 | end_datetime = start_datetime + timedelta(days=n_days) |
||
94 | |||
95 | # Import data. |
||
96 | data = get_data_from_file_path("diesel_genset_data.csv") |
||
97 | |||
98 | # Change the index of data to be able to select data based on the time range. |
||
99 | data.index = pd.date_range(start="2022-01-01", periods=len(data), freq="h") |
||
100 | |||
101 | # Choose the range of the solar potential and demand |
||
102 | # based on the selected simulation period. |
||
103 | solar_potential = data.SolarGen.loc[start_datetime:end_datetime] |
||
104 | hourly_demand = data.Demand.loc[start_datetime:end_datetime] |
||
105 | peak_solar_potential = solar_potential.max() |
||
106 | peak_demand = hourly_demand.max() |
||
107 | |||
108 | # Create the energy system. |
||
109 | date_time_index = pd.date_range( |
||
110 | start=start_date, periods=n_days * 24, freq="h" |
||
111 | ) |
||
112 | energy_system = solph.EnergySystem(timeindex=date_time_index) |
||
113 | |||
114 | # -------------------- BUSES -------------------- |
||
115 | # Create electricity and diesel buses. |
||
116 | b_el_ac = solph.buses.Bus(label="electricity_ac") |
||
117 | b_el_dc = solph.buses.Bus(label="electricity_dc") |
||
118 | b_diesel = solph.buses.Bus(label="diesel") |
||
119 | |||
120 | # -------------------- SOURCES -------------------- |
||
121 | diesel_cost = 0.65 # currency/l |
||
122 | diesel_density = 0.846 # kg/l |
||
123 | diesel_lhv = 11.83 # kWh/kg |
||
124 | diesel_source = solph.components.Source( |
||
125 | label="diesel_source", |
||
126 | outputs={ |
||
127 | b_diesel: solph.flows.Flow( |
||
128 | variable_costs=diesel_cost / diesel_density / diesel_lhv |
||
129 | ) |
||
130 | }, |
||
131 | ) |
||
132 | |||
133 | # EPC stands for the equivalent periodical costs. |
||
134 | epc_pv = 152.62 # currency/kW/year |
||
135 | pv = solph.components.Source( |
||
136 | label="pv", |
||
137 | outputs={ |
||
138 | b_el_dc: solph.flows.Flow( |
||
139 | fix=solar_potential / peak_solar_potential, |
||
140 | nominal_capacity=solph.Investment( |
||
141 | ep_costs=epc_pv * n_days / n_days_in_year |
||
142 | ), |
||
143 | variable_costs=0, |
||
144 | ) |
||
145 | }, |
||
146 | ) |
||
147 | |||
148 | # -------------------- CONVERTERS -------------------- |
||
149 | # The diesel genset does not have a fixed nominal capacity and will be |
||
150 | # optimized using the minimum and maximum loads and efficiencies. |
||
151 | # In this case, the `Investment` and `NonConvex` attributes must be used. The |
||
152 | # combination of these two attributes is utilized in the |
||
153 | # `InvestNonConvexFlowBlock`. |
||
154 | |||
155 | # If the nominal capacity of the genset is already known, only the `NonConvex` |
||
156 | # attribute should be defined, and therefore, the `NonConvexFlowBlock` will |
||
157 | # be used. |
||
158 | |||
159 | # Specify the minimum and maximum loads and the corresponding efficiencies |
||
160 | # for the diesel genset. |
||
161 | min_load = 0.2 |
||
162 | max_load = 1.0 |
||
163 | min_efficiency = 0.20 |
||
164 | max_efficiency = 0.33 |
||
165 | |||
166 | # Calculate the two polynomial coefficients, i.e. the y-intersection and the |
||
167 | # slope of the linear equation. There is a conveniece function for that |
||
168 | # in solph: |
||
169 | slope, offset = solph.components.slope_offset_from_nonconvex_output( |
||
170 | max_load, min_load, max_efficiency, min_efficiency |
||
171 | ) |
||
172 | |||
173 | epc_diesel_genset = 84.80 # currency/kW/year |
||
174 | variable_cost_diesel_genset = 0.045 # currency/kWh |
||
175 | diesel_genset = solph.components.OffsetConverter( |
||
176 | label="diesel_genset", |
||
177 | inputs={b_diesel: solph.flows.Flow()}, |
||
178 | outputs={ |
||
179 | b_el_ac: solph.flows.Flow( |
||
180 | variable_costs=variable_cost_diesel_genset, |
||
181 | min=min_load, |
||
182 | max=max_load, |
||
183 | nominal_capacity=solph.Investment( |
||
184 | ep_costs=epc_diesel_genset * n_days / n_days_in_year, |
||
185 | maximum=2 * peak_demand, |
||
186 | ), |
||
187 | nonconvex=solph.NonConvex(), |
||
188 | ), |
||
189 | }, |
||
190 | conversion_factors={b_diesel: slope}, |
||
191 | normed_offsets={b_diesel: offset}, |
||
192 | ) |
||
193 | |||
194 | # The rectifier assumed to have a fixed efficiency of 98%. |
||
195 | epc_rectifier = 62.35 # currency/kW/year |
||
196 | rectifier = solph.components.Converter( |
||
197 | label="rectifier", |
||
198 | inputs={ |
||
199 | b_el_ac: solph.flows.Flow( |
||
200 | nominal_capacity=solph.Investment( |
||
201 | ep_costs=epc_rectifier * n_days / n_days_in_year |
||
202 | ), |
||
203 | variable_costs=0, |
||
204 | ) |
||
205 | }, |
||
206 | outputs={b_el_dc: solph.flows.Flow()}, |
||
207 | conversion_factors={ |
||
208 | b_el_dc: 0.98, |
||
209 | }, |
||
210 | ) |
||
211 | |||
212 | # The inverter assumed to have a fixed efficiency of 98%. |
||
213 | epc_inverter = 62.35 # currency/kW/year |
||
214 | inverter = solph.components.Converter( |
||
215 | label="inverter", |
||
216 | inputs={ |
||
217 | b_el_dc: solph.flows.Flow( |
||
218 | nominal_capacity=solph.Investment( |
||
219 | ep_costs=epc_inverter * n_days / n_days_in_year |
||
220 | ), |
||
221 | variable_costs=0, |
||
222 | ) |
||
223 | }, |
||
224 | outputs={b_el_ac: solph.flows.Flow()}, |
||
225 | conversion_factors={ |
||
226 | b_el_ac: 0.98, |
||
227 | }, |
||
228 | ) |
||
229 | |||
230 | # -------------------- STORAGE -------------------- |
||
231 | epc_battery = 101.00 # currency/kWh/year |
||
232 | battery = solph.components.GenericStorage( |
||
233 | label="battery", |
||
234 | nominal_capacity=solph.Investment( |
||
235 | ep_costs=epc_battery * n_days / n_days_in_year |
||
236 | ), |
||
237 | inputs={b_el_dc: solph.flows.Flow(variable_costs=0)}, |
||
238 | outputs={ |
||
239 | b_el_dc: solph.flows.Flow( |
||
240 | nominal_capacity=solph.Investment(ep_costs=0) |
||
241 | ) |
||
242 | }, |
||
243 | initial_storage_level=0.0, |
||
244 | min_storage_level=0.0, |
||
245 | max_storage_level=1.0, |
||
246 | balanced=True, |
||
247 | inflow_conversion_factor=0.9, |
||
248 | outflow_conversion_factor=0.9, |
||
249 | invest_relation_input_capacity=1, |
||
250 | invest_relation_output_capacity=0.5, |
||
251 | ) |
||
252 | |||
253 | # -------------------- SINKS -------------------- |
||
254 | demand_el = solph.components.Sink( |
||
255 | label="electricity_demand", |
||
256 | inputs={ |
||
257 | b_el_ac: solph.flows.Flow( |
||
258 | fix=hourly_demand / peak_demand, |
||
259 | nominal_capacity=peak_demand, |
||
260 | ) |
||
261 | }, |
||
262 | ) |
||
263 | |||
264 | excess_el = solph.components.Sink( |
||
265 | label="excess_el", |
||
266 | inputs={b_el_dc: solph.flows.Flow()}, |
||
267 | ) |
||
268 | |||
269 | # Add all objects to the energy system. |
||
270 | energy_system.add( |
||
271 | pv, |
||
272 | diesel_source, |
||
273 | b_el_dc, |
||
274 | b_el_ac, |
||
275 | b_diesel, |
||
276 | inverter, |
||
277 | rectifier, |
||
278 | diesel_genset, |
||
279 | battery, |
||
280 | demand_el, |
||
281 | excess_el, |
||
282 | ) |
||
283 | |||
284 | ########################################################################## |
||
285 | # Optimise the energy system |
||
286 | ########################################################################## |
||
287 | |||
288 | if optimize is False: |
||
289 | return energy_system |
||
290 | |||
291 | # The higher the MipGap or ratioGap, the faster the solver would converge, |
||
292 | # but the less accurate the results would be. |
||
293 | solver_option = {"gurobi": {"MipGap": "0.02"}, "cbc": {"ratioGap": "0.02"}} |
||
294 | solver = "cbc" |
||
295 | |||
296 | model = solph.Model(energy_system) |
||
297 | model.solve( |
||
298 | solver=solver, |
||
299 | solve_kwargs={"tee": True}, |
||
300 | cmdline_options=solver_option[solver], |
||
301 | ) |
||
302 | |||
303 | # End of the calculation time. |
||
304 | end_simulation_time = time.time() |
||
305 | |||
306 | ########################################################################## |
||
307 | # Process the results |
||
308 | ########################################################################## |
||
309 | |||
310 | results = solph.processing.results(model) |
||
311 | |||
312 | results_pv = solph.views.node(results=results, node="pv") |
||
313 | results_diesel_source = solph.views.node( |
||
314 | results=results, node="diesel_source" |
||
315 | ) |
||
316 | results_diesel_genset = solph.views.node( |
||
317 | results=results, node="diesel_genset" |
||
318 | ) |
||
319 | results_inverter = solph.views.node(results=results, node="inverter") |
||
320 | results_rectifier = solph.views.node(results=results, node="rectifier") |
||
321 | results_battery = solph.views.node(results=results, node="battery") |
||
322 | results_demand_el = solph.views.node( |
||
323 | results=results, node="electricity_demand" |
||
324 | ) |
||
325 | results_excess_el = solph.views.node(results=results, node="excess_el") |
||
326 | |||
327 | # -------------------- SEQUENCES (DYNAMIC) -------------------- |
||
328 | # Hourly demand profile. |
||
329 | sequences_demand = results_demand_el["sequences"][ |
||
330 | (("electricity_ac", "electricity_demand"), "flow") |
||
331 | ] |
||
332 | |||
333 | # Hourly profiles for solar potential and pv production. |
||
334 | sequences_pv = results_pv["sequences"][(("pv", "electricity_dc"), "flow")] |
||
335 | |||
336 | # Hourly profiles for diesel consumption and electricity production |
||
337 | # in the diesel genset. |
||
338 | # The 'flow' from oemof is in kWh and must be converted to |
||
339 | # kg by dividing it by the lower heating value and then to |
||
340 | # liter by dividing it by the diesel density. |
||
341 | sequences_diesel_consumption = ( |
||
342 | results_diesel_source["sequences"][ |
||
343 | (("diesel_source", "diesel"), "flow") |
||
344 | ] |
||
345 | / diesel_lhv |
||
346 | / diesel_density |
||
347 | ) |
||
348 | |||
349 | # Hourly profile for the kWh of the diesel provided for the diesel genset |
||
350 | sequences_diesel_consumption_kwh = results_diesel_source["sequences"][ |
||
351 | (("diesel_source", "diesel"), "flow") |
||
352 | ] |
||
353 | |||
354 | sequences_diesel_genset = results_diesel_genset["sequences"][ |
||
355 | (("diesel_genset", "electricity_ac"), "flow") |
||
356 | ] |
||
357 | |||
358 | # Hourly profiles for inverted electricity from dc to ac. |
||
359 | sequences_inverter = results_inverter["sequences"][ |
||
360 | (("inverter", "electricity_ac"), "flow") |
||
361 | ] |
||
362 | |||
363 | # Hourly profiles for inverted electricity from ac to dc. |
||
364 | sequences_rectifier = results_rectifier["sequences"][ |
||
365 | (("rectifier", "electricity_dc"), "flow") |
||
366 | ] |
||
367 | |||
368 | # Hourly profiles for excess ac and dc electricity production. |
||
369 | sequences_excess = results_excess_el["sequences"][ |
||
370 | (("electricity_dc", "excess_el"), "flow") |
||
371 | ] |
||
372 | |||
373 | # -------------------- SCALARS (STATIC) -------------------- |
||
374 | capacity_diesel_genset = results_diesel_genset["scalars"][ |
||
375 | (("diesel_genset", "electricity_ac"), "invest") |
||
376 | ] |
||
377 | |||
378 | # Define a tolerance to force 'too close' numbers to the `min_load` |
||
379 | # and to 0 to be the same as the `min_load` and 0. |
||
380 | # Load is defined here in percentage. |
||
381 | tol = 1e-8 |
||
382 | load_diesel_genset = sequences_diesel_genset / capacity_diesel_genset * 100 |
||
383 | sequences_diesel_genset[np.abs(load_diesel_genset) < tol] = 0 |
||
384 | sequences_diesel_genset[np.abs(load_diesel_genset - min_load) < tol] = ( |
||
385 | min_load * capacity_diesel_genset |
||
386 | ) |
||
387 | sequences_diesel_genset[np.abs(load_diesel_genset - max_load) < tol] = ( |
||
388 | max_load * capacity_diesel_genset |
||
389 | ) |
||
390 | |||
391 | # Calculate the efficiency of the diesel genset. |
||
392 | # If the load is equal to 0, the efficiency will also be 0. |
||
393 | # Efficiency is defined here in percentage. |
||
394 | efficiency_diesel_genset = np.zeros(len(sequences_diesel_genset)) |
||
395 | for i in range(len(sequences_diesel_genset)): |
||
396 | if sequences_diesel_consumption_kwh[i] != 0: |
||
397 | efficiency_diesel_genset[i] = ( |
||
398 | sequences_diesel_genset[i] |
||
399 | / sequences_diesel_consumption_kwh[i] |
||
400 | * 100 |
||
401 | ) |
||
402 | |||
403 | capacity_pv = results_pv["scalars"][(("pv", "electricity_dc"), "invest")] |
||
404 | capacity_inverter = results_inverter["scalars"][ |
||
405 | (("electricity_dc", "inverter"), "invest") |
||
406 | ] |
||
407 | capacity_rectifier = results_rectifier["scalars"][ |
||
408 | (("electricity_ac", "rectifier"), "invest") |
||
409 | ] |
||
410 | capacity_battery = results_battery["scalars"][ |
||
411 | (("electricity_dc", "battery"), "invest") |
||
412 | ] |
||
413 | |||
414 | total_cost_component = ( |
||
415 | ( |
||
416 | epc_diesel_genset * capacity_diesel_genset |
||
417 | + epc_pv * capacity_pv |
||
418 | + epc_rectifier * capacity_rectifier |
||
419 | + epc_inverter * capacity_inverter |
||
420 | + epc_battery * capacity_battery |
||
421 | ) |
||
422 | * n_days |
||
423 | / n_days_in_year |
||
424 | ) |
||
425 | |||
426 | # The only componnet with the variable cost is the diesl genset |
||
427 | total_cost_variable = ( |
||
428 | variable_cost_diesel_genset * sequences_diesel_genset.sum(axis=0) |
||
429 | ) |
||
430 | |||
431 | total_cost_diesel = diesel_cost * sequences_diesel_consumption.sum(axis=0) |
||
432 | total_revenue = ( |
||
433 | total_cost_component + total_cost_variable + total_cost_diesel |
||
434 | ) |
||
435 | total_demand = sequences_demand.sum(axis=0) |
||
436 | |||
437 | # Levelized cost of electricity in the system in currency's Cent per kWh. |
||
438 | lcoe = 100 * total_revenue / total_demand |
||
439 | |||
440 | # The share of renewable energy source used to cover the demand. |
||
441 | res = ( |
||
442 | 100 |
||
443 | * sequences_pv.sum(axis=0) |
||
444 | / (sequences_diesel_genset.sum(axis=0) + sequences_pv.sum(axis=0)) |
||
445 | ) |
||
446 | |||
447 | # The amount of excess electricity (which must probably be dumped). |
||
448 | excess_rate = ( |
||
449 | 100 |
||
450 | * sequences_excess.sum(axis=0) |
||
451 | / (sequences_excess.sum(axis=0) + sequences_demand.sum(axis=0)) |
||
452 | ) |
||
453 | |||
454 | ########################################################################## |
||
455 | # Print the results in the terminal |
||
456 | ########################################################################## |
||
457 | |||
458 | if __name__ == "__main__": |
||
459 | print("\n" + 50 * "*") |
||
460 | print( |
||
461 | f"Simulation Time: {end_simulation_time-start_simulation_time:.2f} s" |
||
462 | ) |
||
463 | print(50 * "*") |
||
464 | print(f"Peak Demand:\t {sequences_demand.max():.0f} kW") |
||
465 | print(f"LCOE:\t\t {lcoe:.2f} cent/kWh") |
||
466 | print(f"RES:\t\t {res:.0f}%") |
||
467 | print(f"Excess:\t\t {excess_rate:.1f}% of the total production") |
||
468 | print(50 * "*") |
||
469 | print("Optimal Capacities:") |
||
470 | print("-------------------") |
||
471 | print(f"Diesel Genset:\t {capacity_diesel_genset:.0f} kW") |
||
472 | print(f"PV:\t\t {capacity_pv:.0f} kW") |
||
473 | print(f"Battery:\t {capacity_battery:.0f} kW") |
||
474 | print(f"Inverter:\t {capacity_inverter:.0f} kW") |
||
475 | print(f"Rectifier:\t {capacity_rectifier:.0f} kW") |
||
476 | print(50 * "*") |
||
477 | |||
478 | ########################################################################## |
||
479 | # Plot the duration curve for the diesel genset |
||
480 | ########################################################################## |
||
481 | |||
482 | if plt is not None: |
||
483 | # Create the duration curve for the diesel genset. |
||
484 | fig1, ax = plt.subplots(figsize=(10, 5)) |
||
485 | |||
486 | # Sort the power generated by the diesel genset in a descending order. |
||
487 | diesel_genset_duration_curve = np.sort(sequences_diesel_genset)[ |
||
488 | ::-1 |
||
489 | ] |
||
490 | |||
491 | percentage = ( |
||
492 | 100 |
||
493 | * np.arange(1, len(diesel_genset_duration_curve) + 1) |
||
494 | / len(diesel_genset_duration_curve) |
||
495 | ) |
||
496 | |||
497 | ax.scatter( |
||
498 | percentage, |
||
499 | diesel_genset_duration_curve, |
||
500 | color="dodgerblue", |
||
501 | marker="+", |
||
502 | ) |
||
503 | |||
504 | # Plot a horizontal line representing the minimum load |
||
505 | ax.axhline( |
||
506 | y=min_load * capacity_diesel_genset, |
||
507 | color="crimson", |
||
508 | linestyle="--", |
||
509 | ) |
||
510 | min_load_annotation_text = ( |
||
511 | f"minimum load: {min_load * capacity_diesel_genset:0.2f} kW" |
||
512 | ) |
||
513 | ax.annotate( |
||
514 | min_load_annotation_text, |
||
515 | xy=(100, min_load * capacity_diesel_genset), |
||
516 | xytext=(0, 5), |
||
517 | textcoords="offset pixels", |
||
518 | horizontalalignment="right", |
||
519 | verticalalignment="bottom", |
||
520 | ) |
||
521 | |||
522 | # Plot a horizontal line representing the maximum load |
||
523 | ax.axhline( |
||
524 | y=max_load * capacity_diesel_genset, |
||
525 | color="crimson", |
||
526 | linestyle="--", |
||
527 | ) |
||
528 | max_load_annotation_text = ( |
||
529 | f"maximum load: {max_load * capacity_diesel_genset:0.2f} kW" |
||
530 | ) |
||
531 | ax.annotate( |
||
532 | max_load_annotation_text, |
||
533 | xy=(100, max_load * capacity_diesel_genset), |
||
534 | xytext=(0, -5), |
||
535 | textcoords="offset pixels", |
||
536 | horizontalalignment="right", |
||
537 | verticalalignment="top", |
||
538 | ) |
||
539 | |||
540 | ax.set_title( |
||
541 | "Duration Curve for the Diesel Genset Electricity Production", |
||
542 | fontweight="bold", |
||
543 | ) |
||
544 | ax.set_ylabel("diesel genset production [kW]") |
||
545 | ax.set_xlabel("percentage of annual operation [%]") |
||
546 | |||
547 | # Create the second axis on the right side of the diagram |
||
548 | # representing the operation load of the diesel genset. |
||
549 | second_ax = ax.secondary_yaxis( |
||
550 | "right", |
||
551 | functions=( |
||
552 | lambda x: x / capacity_diesel_genset * 100, |
||
|
|||
553 | lambda x: x * capacity_diesel_genset / 100, |
||
554 | ), |
||
555 | ) |
||
556 | second_ax.set_ylabel("diesel genset operation load [%]") |
||
557 | |||
558 | ####################################################################### |
||
559 | # Plot the efficiency curve for the diesel genset |
||
560 | ####################################################################### |
||
561 | |||
562 | fig2, ax = plt.subplots(figsize=(10, 5)) |
||
563 | ax.scatter( |
||
564 | load_diesel_genset, |
||
565 | efficiency_diesel_genset, |
||
566 | marker="+", |
||
567 | ) |
||
568 | |||
569 | # Plot a horizontal line representing the minimum efficiency |
||
570 | ax.axhline( |
||
571 | y=min_efficiency * 100, |
||
572 | color="crimson", |
||
573 | linestyle="--", |
||
574 | ) |
||
575 | min_efficiency_annotation_text = ( |
||
576 | f"minimum efficiency: {min_efficiency*100:0.0f}%" |
||
577 | ) |
||
578 | ax.annotate( |
||
579 | min_efficiency_annotation_text, |
||
580 | xy=(100, min_efficiency * 100), |
||
581 | xytext=(0, 10), |
||
582 | textcoords="offset pixels", |
||
583 | horizontalalignment="right", |
||
584 | verticalalignment="bottom", |
||
585 | ) |
||
586 | |||
587 | # Plot a horizontal line representing the maximum efficiency |
||
588 | ax.axhline( |
||
589 | y=max_efficiency * 100, |
||
590 | color="crimson", |
||
591 | linestyle="--", |
||
592 | ) |
||
593 | max_efficiency_annotation_text = ( |
||
594 | f"maximum efficiency: {max_efficiency*100:0.0f}%" |
||
595 | ) |
||
596 | ax.annotate( |
||
597 | max_efficiency_annotation_text, |
||
598 | xy=(100, max_efficiency * 100), |
||
599 | xytext=(0, 10), |
||
600 | textcoords="offset pixels", |
||
601 | horizontalalignment="right", |
||
602 | verticalalignment="bottom", |
||
603 | ) |
||
604 | |||
605 | ax.set_title( |
||
606 | "Efficiency Curve for Different Loads of the Diesel Genset", |
||
607 | fontweight="bold", |
||
608 | ) |
||
609 | ax.set_ylabel("efficiency [%]") |
||
610 | ax.set_xlabel("diesel genset load [%]") |
||
611 | |||
612 | ax.set_xlim(min_load * 100 - 5, max_load * 100 + 5) |
||
613 | |||
614 | plt.show() |
||
615 | |||
619 |