1
# Copyright (c) 2020 by Fraunhofer Institute for Energy Economics
2
# and Energy System Technology (IEE), Kassel. All rights reserved.
3
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
4

5 1
import os
6 1
import pandas as pd
7 1
import numpy as np
8 1
from pandapipes import pp_dir
9 1
from pandapower.io_utils import JSONSerializableClass
10 1
from scipy.interpolate import interp1d
11

12 1
try:
13 1
    import pplog as logging
14 1
except ImportError:
15 1
    import logging
16

17 1
logger = logging.getLogger(__name__)
18

19

20 1
class Fluid(JSONSerializableClass):
21
    """
22

23
    """
24

25 1
    def __init__(self, name, fluid_type, **kwargs):
26
        """
27

28
        :param name:
29
        :type name:
30
        :param fluid_type:
31
        :type fluid_type:
32
        :param kwargs:
33
        :type kwargs:
34
        """
35 1
        super(Fluid, self).__init__()
36 1
        self.name = name
37 1
        if not isinstance(fluid_type, str) or fluid_type.lower() not in ["gas", "liquid"]:
38 0
            logger.warning("The fluid %s has the fluid type %s which might cause problems in the "
39
                           "pipeflow calculation, as it expects either 'gas' or 'liquid'."
40
                           % (name, fluid_type))
41 1
        self.fluid_type = fluid_type.lower()
42 1
        self.is_gas = self.fluid_type == "gas"
43 1
        self.all_properties = kwargs
44 1
        for prop_name, prop in self.all_properties.items():
45 1
            if not isinstance(prop, FluidProperty):
46 0
                logger.warning("The property %s was not defined as a fluid property. This might "
47
                               "cause problems when trying to ask for values." % prop_name)
48

49 1
    def __repr__(self):
50 1
        r = "Fluid %s (%s) with properties:" % (self.name, self.fluid_type)
51 1
        for key in self.all_properties.keys():
52 1
            r += "\n   - %s (%s)" %(key, self.all_properties[key].__class__.__name__[13:])
53 1
        return r
54

55 1
    def add_property(self, property_name, prop, overwrite=True, warn_on_duplicates=True):
56
        """
57
        This function adds a new property.
58

59
        :param property_name: Name of the new property
60
        :type property_name: str
61
        :param prop: Values for the property, for example a curve or just a constant value
62
        :type prop: pandapipes.FluidProperty
63
        :param overwrite: True if existing property with the same name shall be overwritten
64
        :type overwrite: bool
65
        :param warn_on_duplicates: True, if a warning of properties with the same name should be
66
                                    returned
67
        :type warn_on_duplicates: bool
68

69
        :Example:
70
            >>> fluid.add_property('water_density', pandapipes.FluidPropertyConstant(998.2061), overwrite=True, warn_on_duplicates=False)
71

72
        """
73 1
        if property_name in self.all_properties:
74 1
            if warn_on_duplicates:
75 1
                ow_string = "It will be overwritten." if overwrite else "It will not be replaced."
76 1
                logger.warning("The property %s already exists. %s" % (property_name, ow_string))
77 1
            if not overwrite:
78 1
                return
79 1
        self.all_properties[property_name] = prop
80

81 1
    def get_property(self, property_name, *at_values):
82
        """
83
        This function returns the value of the requested property.
84

85
        :param property_name: Name of the searched property
86
        :type property_name: str
87
        :param at_values: Value for which the property should be returned
88
        :type at_values:
89
        :return: Returns property at the certain value
90
        :rtype: pandapipes.FluidProperty
91
        """
92

93 1
        if property_name not in self.all_properties:
94 1
            raise UserWarning("The property %s was not defined for the fluid %s"
95
                              % (property_name, self.name))
96 1
        return self.all_properties[property_name].get_property(*at_values)
97

98 1
    def get_density(self, temperature):
99
        """
100
        This function returns the density at a certain temperature.
101

102
        :param temperature: Temperature at which the density is queried
103
        :type temperature: float
104
        :return: Density at the required temperature
105

106
        """
107

108 1
        return self.get_property("density", temperature)
109

110 1
    def get_viscosity(self, temperature):
111
        """
112
        This function returns the viscosity at a certain temperature.
113

114
        :param temperature: Temperature at which the viscosity is queried
115
        :type temperature: float
116
        :return: Viscosity at the required temperature
117

118
        """
119

120 1
        return self.get_property("viscosity", temperature)
121

122 1
    def get_heat_capacity(self, temperature):
123
        """
124
        This function returns the heat capacity at a certain temperature.
125

126
        :param temperature: Temperature at which the heat capacity is queried
127
        :type temperature: float
128
        :return: Heat capacity at the required temperature
129

130
        """
131

132 1
        return self.get_property("heat_capacity", temperature)
133

134 1
    def get_molar_mass(self):
135
        """
136
        This function returns the molar mass.
137

138
        :return: molar mass
139

140
        """
141

142 0
        return self.get_property("molar_mass")
143

144 1
    def get_compressibility(self, temperature):
145
        """
146
        This function returns the compressibility at a certain temperature.
147

148
        :param temperature: Temperature at which the compressibility is queried
149
        :type temperature: float
150
        :return: compressibility at the required temperature
151

152
        """
153

154 0
        return self.get_property("compressibility", temperature)
155

156 1
    def get_der_compressibility(self):
157
        """
158
        This function returns the derivative of the compressibility.
159

160
        :return: derivative of the compressibility
161

162
        """
163

164 0
        return self.get_property("der_compressibility")
165

166

167 1
class FluidProperty(JSONSerializableClass):
168
    """
169
    Property Base Class
170
    """
171

172 1
    def __init__(self):
173 1
        super().__init__()
174

175 1
    def get_property(self, *args):
176
        """
177

178
        :param arg:
179
        :type arg:
180
        :return:
181
        :rtype:
182
        """
183 1
        raise NotImplementedError("Please implement a proper fluid property!")
184

185

186 1
class FluidPropertyInterExtra(FluidProperty):
187
    """
188
    Creates Property with interpolated or extrapolated values.
189
    """
190 1
    json_excludes = JSONSerializableClass.json_excludes + ["prop_getter"]
191 1
    prop_getter_entries = {"x": "x", "y": "y", "_fill_value_orig": "fill_value"}
192

193 1
    def __init__(self, x_values, y_values, method="interpolate_extrapolate"):
194
        """
195

196
        :param x_values:
197
        :type x_values:
198
        :param y_values:
199
        :type y_values:
200
        :param method:
201
        :type method:
202
        """
203 1
        super(FluidPropertyInterExtra, self).__init__()
204 1
        if method.lower() == "interpolate_extrapolate":
205 1
            self.prop_getter = interp1d(x_values, y_values, fill_value="extrapolate")
206
        else:
207 0
            self.prop_getter = interp1d(x_values, y_values)
208

209 1
    def get_property(self, arg):
210
        """
211

212
        :param arg: Name of the property and one or more values (x-values) for which the y-values of the property are to be displayed
213
        :type arg: str, float or array
214
        :return: y-value/s
215
        :rtype: float, array
216
        """
217 1
        return self.prop_getter(arg)
218

219 1
    @classmethod
220 1
    def from_path(cls, path, method="interpolate_extrapolate"):
221
        """
222
        Reads a text file with temperature values in the first column and property values in
223
        second column.
224

225
        :param path: Target path of the txt file
226
        :type path: str
227
        :param method: Method with which the values are to be interpolated
228
        :type method: str
229
        :return: interpolated values
230
        :rtype: pandapipes.FluidProperty
231
        """
232 1
        values = np.loadtxt(path)
233 1
        return cls(values[:, 0], values[:, 1], method=method)
234

235 1
    def to_dict(self):
236 1
        d = super(FluidPropertyInterExtra, self).to_dict()
237 1
        d.update({k: self.prop_getter.__dict__[k] for k in self.prop_getter_entries.keys()})
238
        # d.update({"x_values": self.prop_getter.x, "y_values": self.prop_getter.y,
239
        #           "method": "interpolate_extrapolate"
240
        #           if self.prop_getter.fill_value == "extrapolate" else None})
241 1
        return d
242

243 1
    @classmethod
244
    def from_dict(cls, d):
245 1
        obj = JSONSerializableClass.__new__(cls)
246 1
        d2 = {cls.prop_getter_entries[k]: v for k, v in d.items()
247
              if k in cls.prop_getter_entries.keys()}
248 1
        d3 = {k: v for k, v in d.items() if k not in cls.prop_getter_entries.keys()}
249 1
        d3["prop_getter"] = interp1d(**d2)
250 1
        obj.__dict__.update(d3)
251 1
        return obj
252

253

254 1
class FluidPropertyConstant(FluidProperty):
255
    """
256
    Creates Property with a constant value.
257
    """
258

259 1
    def __init__(self, value):
260
        """
261

262
        :param value:
263
        :type value:
264
        """
265 1
        super(FluidPropertyConstant, self).__init__()
266 1
        self.value = value
267

268 1
    def get_property(self, *args):
269
        """
270

271
        :param arg: Name of the property
272
        :type arg: str
273
        :return: Value of the property
274
        :rtype: float
275

276
        :Example:
277
            >>> heat_capacity = get_fluid(net).get_property("heat_capacity")
278
        """
279 1
        if len(args) > 1:
280 0
            raise(UserWarning('Please define either none or an array-like argument'))
281 1
        elif len(args) == 1:
282 1
            logger.warning('One constant property has several input variables even though it is '
283
                           'independent of these')
284 1
            output = np.array([self.value]) * np.ones(len(args[0]))
285
        else:
286 0
            output = np.array([self.value])
287 1
        return  output
288

289 1
    @classmethod
290
    def from_path(cls, path):
291
        """
292
        Reads a text file with temperature values in the first column and property values in
293
        second column.
294

295
        :param path:
296
        :type path:
297
        :param method:
298
        :type method:
299
        :return:
300
        :rtype:
301
        """
302 1
        value = np.loadtxt(path).item()
303 1
        return cls(value)
304

305

306 1
class FluidPropertyLinear(FluidProperty):
307
    """
308
    Creates Property with a linear course.
309
    """
310

311 1
    def __init__(self, slope, offset):
312
        """
313

314
        :param slope:
315
        :type slope:
316
        :param offset:
317
        :type offset:
318

319
        """
320 1
        super(FluidPropertyLinear, self).__init__()
321 1
        self.slope = slope
322 1
        self.offset = offset
323

324 1
    def get_property(self, arg):
325
        """
326

327
        :param arg: Name of the property and one or more values (x-values) for which the function of the property should be calculated
328
        :type arg: str, float or array
329
        :return: y-value or function values
330
        :rtype: float, array
331

332
        :Example:
333
            >>> comp_fact = get_fluid(net).get_property("compressibility", p_bar)
334

335
        """
336 1
        if type(arg) == pd.Series:
337 0
            return self.offset + self.slope * arg.values
338
        else:
339 1
            return self.offset + self.slope * np.array(arg)
340

341 1
    @classmethod
342
    def from_path(cls, path):
343
        """
344
        Reads a text file with temperature values in the first column and property values in
345
        second column.
346

347
        :param path:
348
        :type path:
349
        :param method:
350
        :type method:
351
        :return:
352
        :rtype:
353
        """
354 1
        slope, offset = np.loadtxt(path)
355 1
        return cls(slope, offset)
356

357

358 1
def create_constant_property(net, property_name, value, overwrite=True, warn_on_duplicates=True):
359
    """
360
    Creates a property with a constant value.
361

362
    :param net: Name of the network to which the property is added
363
    :type net: pandapipesNet
364
    :param property_name: Name of the new property
365
    :type property_name: str
366
    :param value: Constant value of the property
367
    :type value: float
368
    :param overwrite:  True if existing property with the same name shall be overwritten
369
    :type overwrite: basestring
370
    :param warn_on_duplicates: True, if a warning of properties with the same name should be
371
                                returned
372
    :type warn_on_duplicates: basestring
373
    """
374 1
    prop = FluidPropertyConstant(value)
375 1
    get_fluid(net).add_property(property_name, prop, overwrite=overwrite,
376
                                warn_on_duplicates=warn_on_duplicates)
377 1
    return prop
378

379

380 1
def create_linear_property(net, property_name, slope, offset, overwrite=True,
381
                           warn_on_duplicates=True):
382
    """
383
    Creates a property with a linear correlation.
384

385
    :param net: Name of the network to which the property is added
386
    :type net: pandapipesNet
387
    :param property_name: Name of the new property
388
    :type property_name: str
389
    :param slope: Slope of the linear correlation
390
    :type slope: float
391
    :param offset: Offset of the linear function
392
    :type offset: float
393
    :param overwrite:  True if existing property with the same name shall be overwritten
394
    :type overwrite: basestring
395
    :param warn_on_duplicates: True, if a warning of properties with the same name should be
396
                                returned
397
    :type warn_on_duplicates: basestring
398
    """
399 0
    prop = FluidPropertyLinear(slope, offset)
400 0
    get_fluid(net).add_property(property_name, prop, overwrite=overwrite,
401
                                warn_on_duplicates=warn_on_duplicates)
402 0
    return prop
403

404

405 1
def create_constant_fluid(name=None, fluid_type=None, **kwargs):
406
    """
407
    Creates a constant fluid.
408

409
    :param name: Name of the fluid
410
    :type name: str
411
    :param fluid_type: Type of the fluid
412
    :type fluid_type: str
413
    :param kwargs: Additional information
414
    :return: Fluid
415
    :rtype: Fluid
416
    """
417 1
    properties = dict()
418 1
    for prop_name, prop in kwargs.items():
419 1
        properties[str(prop_name)] = FluidPropertyConstant(prop)
420

421 1
    return Fluid(name=name, fluid_type=fluid_type, **properties)
422

423

424 1
def call_lib(fluid):
425
    """
426
    Creates a fluid with default fluid properties.
427

428
    :param fluid: Fluid which should be used
429
    :type fluid: str
430
    :return: Fluid - Chosen fluid with default fluid properties
431
    :rtype: Fluid
432
    """
433

434 1
    def interextra_property(prop):
435 1
        return FluidPropertyInterExtra.from_path(
436
            os.path.join(pp_dir, "properties", fluid, prop + ".txt"))
437

438 1
    def constant_property(prop):
439 1
        return FluidPropertyConstant.from_path(
440
            os.path.join(pp_dir, "properties", fluid, prop + ".txt"))
441

442 1
    def linear_property(prop):
443 1
        return FluidPropertyLinear.from_path(
444
            os.path.join(pp_dir, "properties", fluid, prop + ".txt"))
445

446 1
    liquids = ["water"]
447 1
    gases = ["air", "lgas", "hgas", "hydrogen"]
448

449 1
    if fluid == "natural_gas":
450 1
        logger.error("'natural_gas' is ambigious. Please choose 'hgas' or 'lgas' "
451
                     "(high- or low calorific natural gas)")
452 1
    if fluid not in liquids and fluid not in gases:
453 1
        raise AttributeError("Fluid '%s' not found in the fluid library. It might not be "
454
                             "implemented yet." % fluid)
455

456 1
    density = interextra_property("density")
457 1
    viscosity = interextra_property("viscosity")
458 1
    heat_capacity = interextra_property("heat_capacity")
459 1
    molar_mass = constant_property("molar_mass")
460 1
    der_compr = constant_property("der_compressibility")
461 1
    compr = linear_property("compressibility")
462

463 1
    phase = "liquid" if fluid in liquids else "gas"
464 1
    return Fluid(fluid, phase, density=density, viscosity=viscosity, heat_capacity=heat_capacity,
465
                 molar_mass=molar_mass,
466
                 compressibility=compr, der_compressibility=der_compr)
467

468

469 1
def get_fluid(net):
470
    """
471
    This function shows which fluid is used in the net.
472

473
    :param net: Current network
474
    :type net: pandapipesNet
475
    :return: Fluid - Name of the fluid which is used in the current network
476
    :rtype: Fluid
477
    """
478 1
    if "fluid" not in net or net["fluid"] is None:
479 1
        raise UserWarning("There is no fluid defined for the given net!")
480 1
    fluid = net["fluid"]
481 1
    if not isinstance(fluid, Fluid):
482 1
        logger.warning("The fluid in this net is not of the pandapipes Fluid type. This could lead"
483
                       " to errors, as some components might depend on this structure")
484 1
    return fluid
485

486

487 1
def _add_fluid_to_net(net, fluid, overwrite=True):
488
    """
489
    Adds a fluid to a net. If overwrite is False, a warning is printed and the fluid is not set.
490

491
    :param net: The pandapipes network for which to set fluid
492
    :type net: pandapipesNet
493
    :param fluid: fluid which to insert into the network
494
    :type fluid: Fluid
495
    :param overwrite: If True, an existing fluid will just be overwritten, otherwise a warning is\
496
        printed out and the fluid is not reset.
497
    :type overwrite: bool, default True
498
    :return: Not output.
499
    """
500 1
    if "fluid" in net and net["fluid"] is not None and not overwrite:
501 1
        fluid_msg = "an existing fluid" if not hasattr(net["fluid"], "name") \
502
            else "the fluid %s" % net["fluid"].name
503 1
        logger.warning("The fluid %s would replace %s and thus cannot be created. Try to set "
504
                       "overwrite to True" % (fluid.name, fluid_msg))
505 1
        return
506

507 1
    if isinstance(fluid, str):
508 0
        logger.warning("Instead of a pandapipes.Fluid, a string ('%s') was passed to the fluid "
509
                       "argument. Internally, it will be passed to call_lib(fluid) to get the "
510
                       "respective pandapipes.Fluid." %fluid)
511 0
        fluid = call_lib(fluid)
512 1
    net["fluid"] = fluid

Read our documentation on viewing source code .

Loading