Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace CoolProp #232

Merged
merged 11 commits into from
Oct 30, 2022
Merged

Conversation

mitchute
Copy link
Contributor

PR replaces the CoolProp fluid properties library with the lighter, zero-dependency library SecondaryCoolantProps. CoolProp has historically been the go-to library for refrigerant and coolant props, but development activity and support as of recent years have been waning, and up until recently versions >3.8 were unsupported. It's unclear if this will persist into the future, so relying on that dependency limits the usability of pygfunction and introduces uncertainty into the project.

SCP is an ultra-lightweight, zero-dependency library focused just on secondary coolants, which IMO should be enough for this application.

@j-c-cook
Copy link
Contributor

SecondaryCoolantProps appears to be a package Matt has put together. The supported fluids cover what are commonly used as the circulating fluid in GHE design:

  • ethyl alcohol
  • ethylene glycol
  • methyl alcohol
  • propylene glycol
  • water

The fluids are implemented as derived objects and have double inheritance from a BaseMelinder class, which depends on an abstract base class, BaseFluid. The abstract methods are overridden in the derived fluid classes.

Given that coolprop has been the standard for a while, is it possible for the tests in SecondaryCoolantProps to utilize coolprop?

@mitchute
Copy link
Contributor Author

@j-c-cook no, we're not going to do that. The test values came from there, but I'm not going to introduce that dependency into our testing. As I said, CP development has been waning, so relying on them for our tests is a step in the wrong direction.

@j-c-cook
Copy link
Contributor

The test values do not have to be magic numbers if they are read from an external file (e.g. json or csv). A script could be written (not a dependency of tests) that generates the external test value file. If I am understanding your comment correctly, you are currently following a similar process, but without showing your work. Opinion: Magic numbers should be avoided when possible.

@MassimoCimmino I think a lighter weight fluid property package would be beneficial, and could help resolve #204.

@Myoldmopar
Copy link

@j-c-cook if I am understanding you correctly, you are just wanting to move the magic numbers out of a python unit test file and into an external file. I don't think this is necessary. The physical property data isn't going to change, so looking up values one time and hard coding them into the unit tests is a reasonable approach. While magic numbers in the program code itself are generally frowned upon, I don't think the argument against magic numbers works as well when you are talking about unit tests comparing function outputs against known fixed meaningful physical property values.

@j-c-cook
Copy link
Contributor

j-c-cook commented Oct 18, 2022

The coolprop package has been the standard for some time. The SecondaryCoolProps package intends to replace the standard for use in pygfunction. The results created by SecondaryCoolProps package have been validated against coolprop.

I agree with Matt that making coolprop a dependency in the test suite is not a good approach. I was previously mistaken, but now am not thanks to Matt's correction. However, I am proposing visibility of the script that used coolprop to generate the test values. I am a proponent of the script for at least two reasons:

  1. The test values generated by coolprop could be placed into a file that is read by the test scripts, which could remove the human from the loop (e.g. Matt says he copied the results from a console output).
  2. Auditing the coolprop generated values is simpler if the script is provided, rather than solely the results.

@MassimoCimmino
Copy link
Owner

MassimoCimmino commented Oct 21, 2022

Thank you @mitchute for the contribution. I will try and address all points.

  • Python compatibility is an issue (python 3.9+ compatibility #204), and Coolprop is the only barrier for pygfunction to support all current Python versions. The project is in no rush to support Python 3.9+, and this may pose additional issues in the future. They are managing a vast and impressive package that handles much more fluids than necessary for pygfunction, and we only care about the Python interface.
  • While we can argue Coolprop is a standard for property evaluation, for our usage, it simply implements published methods/correlations from the litterature. Antifreeze mixtures are bases on Melinder's work in Coolprop. From that perspective, SecondaryCoolantProps is up to the standard. From my understanding, it may grow to cover more fluids relevant to geothermal and buildings.
  • Reference test results in pygfunction use hardcoded numbers generated during the implementation of their associated features. This serves the purpose of detecting breaking changes at the PR step. Validation is separate from that process and should be handled when working on an issue. I've been thinking of a directory for validation cases which would be different from the current examples directory (this one should strictly be about how to use pygfunction). This is outside the scope of the current issue and the discussion on hardcoded numbers could be moved to Overhaul tests and increase coverage #181.
  • SecondaryCoolantProps is a separate project from pygfunction with its own governance. pygfunction should adapt to its interface just like it did for Coolprop. The main issue from pygfunction's perpective is to confirm that the interface currently available in v1.0 is stable and will not inadvertently break pygfunction on a new release. @mitchute should be able to clarify their intentions with the project. I also like the idea of a common interface for mixtures but that can be discussed on the linked issue (Add fluid handler object mitchute/SecondaryCoolantProps#8).

@j-c-cook
Copy link
Contributor

@MassimoCimmino I have started a validation of SecondaryCoolantProps to coolprop in main.py. The requirements I am using are defined in requirements.txt.

I am getting some error levels that I really wasn't expecting from work done by @mitchute and @Myoldmopar. I am assuming that I have made a mistake somewhere. Could you please check my work? The current absolute errors I am seeing are:

MEA max abs errors: rho : 11.80%  mu : 273.90%  cp : 20.78%  k : 55.20%  Pr : 453.64%  
MEG max abs errors: rho : 8.77%  mu : 501.78%  cp : 29.33%  k : 41.57%  Pr : 584.94%  
MPG max abs errors: rho : 5.52%  mu : 1468.65%  cp : 22.65%  k : 47.48%  Pr : 2050.29% 
MMA max abs errors: rho : 11.24%  mu : 102.50%  cp : 24.50%  k : 50.14%  Pr : 161.20% 

The script calculates fluid properties in pygfunction 2.2.1 and j-c-cook/SecondaryCoolantProps@ad1c480d105d6ef14a6793671dc1395355bb4470 over a 2-dimensional range. The concentration and temperature values are varied. A np.meshgrid is used. The temperatures are currently all varied from 0.1-40 Celsius while the mixtures are varied from 0.0001 to 0.6. Folders are created based on the mixer name, and contour plots are placed inside of the relevant sub-directory.

@j-c-cook
Copy link
Contributor

Fixed my error here. The pygfunction API requires a percentage, while the SecondaryCoolantProps API takes a ratio. I'm now computing the following max absolute errors:

MEA max abs errors: rho : 0.16%  mu : 10.81%  cp : 0.68%  k : 0.66%  Pr : 11.29%  
MEG max abs errors: rho : 0.00%  mu : 0.00%  cp : 0.00%  k : 0.00%  Pr : 0.00%  
MPG max abs errors: rho : 0.00%  mu : 0.00%  cp : 0.00%  k : 0.00%  Pr : 0.00%  
MMA max abs errors: rho : 0.02%  mu : 2.07%  cp : 0.09%  k : 0.18%  Pr : 2.28%  

MEG and MPG appear to produce nearly identical results, while MEA and MMA do have some differences. (Note: There could be an error in this analysis. A second set of eyes would be helpful.)

@j-c-cook
Copy link
Contributor

Sources of error in SecondaryCoolantProps when compared to coolprop appear when the input temperature is low (approximately less than 1.5 C). For example:

mu
Pr

@MassimoCimmino
Copy link
Owner

MassimoCimmino commented Oct 23, 2022

There may be an issue with freeze point temperatures.

>>> import numpy as np
>>> from scp.ethylene_glycol import EthyleneGlycol
>>> fluid = EthyleneGlycol(0.1)
>>> x = np.array([0.0, 0.05, 0.1, 0.1395, 0.20, 0.235, 0.30, 0.305, 0.362, 0.40, 0.455, 0.50, 0.526, 0.60])
>>> np.array([fluid.calc_freeze_point(xi) for xi in x])
array([ 0.        ,  0.03982412,  0.02484243,  0.01299708, -0.00516268,
       -0.01567769, -0.03522389, -0.03672844, -0.05389027, -0.06534179,
       -0.081931  , -0.09551694, -0.10337194, -0.12574994])
>>> import numpy as np
>>> from scp.propylene_glycol import PropyleneGlycol
>>> fluid = PropyleneGlycol(0.1)
>>> x = np.array([0.05, 0.10, 0.156, 0.20, 0.254, 0.30, 0.329, 0.3939, 0.40, 0.4882, 0.50, 0.55, 0.60])
>>> np.array([fluid.calc_freeze_point(xi) for xi in x])
array([-0.00966551, -0.02649032, -0.04531757, -0.06009826, -0.07822375,
       -0.09365154, -0.10337194, -0.12510934, -0.12715132, -0.15665462,
       -0.16059873, -0.17730315, -0.19399492])
>>> import numpy as np
>>> from scp.ethyl_alcohol import EthylAlcohol
>>> fluid = EthylAlcohol(0.1)
>>> x = np.array([0.05, 0.10, 0.111, 0.15, 0.187, 0.20, 0.245, 0.25, 0.297, 0.30, 0.40, 0.407, 0.50, 0.531, 0.60])
>>> np.array([fluid.calc_freeze_point(xi) for xi in x])
array([2.41924585, 2.37007532, 2.35926901, 2.32098826, 2.2847175 ,
       2.27198454, 2.22795233, 2.22306402, 2.17715448, 2.17422657,
       2.07680032, 2.06999288, 1.97970469, 1.949672  , 1.88293858])
>>> import numpy as np
>>> from scp.methyl_alcohol import MethylAlcohol
>>> fluid = MethylAlcohol(0.1)
>>> x = np.array([0.05, 0.0786, 0.10, 0.1436, 0.15, 0.1995, 0.2487, 0.30, 0.3348, 0.40, 0.4095, 0.50, 0.60])
>>> np.array([fluid.calc_freeze_point(xi) for xi in x])
array([0.66461146, 0.64827578, 0.63603887, 0.61107132, 0.60740226,
       0.57898899, 0.5506858 , 0.52110864, 0.50100631, 0.46326005,
       0.45775115, 0.40515615, 0.34679721])

@mitchute
Copy link
Contributor Author

@MassimoCimmino Thanks for pointing that out. When I correlated the freezing points I mistakenly kept the concentration in percent, instead of fraction of concentration. The new version has fixed this and changed the freezing point calc over to the Melinder correlations also.

Responding to your points above:

Python compatibility is an issue

Yes, this is a major reason for this work. We would like these relatively simple fluid property routines to not continue to be a bottleneck for this and other projects. I will say that I've run into some issues testing with Python 3.10, but that's because the collections module has changed its interface. Hopefully, that gets sorted out eventually, but that's nothing to do with either of these projects.

Coolprop is a standard for property evaluation, for our usage, it simply implements published methods/correlations from the litterature

Agreed - these are standard correlations by Melinder 2010, or CRC Handbook in the case of water.

Reference test results in pygfunction use hardcoded numbers generated during the implementation of their associated features.

In the case of physical properties, hardcoded numbers are fine IMO. A better way in some cases would be to run multiple versions (base branch and mod branch) and then compare results. The CI system here isn't so sophisticated, so having hardcoded numbers at least lets us know when something has broken, or even changed for valid reasons.

The main issue from pygfunction's perpective is to confirm that the interface currently available in v1.0 is stable

Yes, we will keep it stable.

@j-c-cook
Copy link
Contributor

MEA max abs errors: rho : 0.00%  mu : 0.00%  cp : 0.00%  k : 0.00%  Pr : 0.00%  
MEG max abs errors: rho : 0.00%  mu : 0.00%  cp : 0.00%  k : 0.00%  Pr : 0.00%  
MPG max abs errors: rho : 0.00%  mu : 0.00%  cp : 0.00%  k : 0.00%  Pr : 0.00%  
MMA max abs errors: rho : 0.00%  mu : 0.00%  cp : 0.00%  k : 0.00%  Pr : 0.00%  

@MassimoCimmino
Copy link
Owner

Tests pass on all versions from 3.7 to 3.11. I will do a final review and merge in the coming days.

@MassimoCimmino
Copy link
Owner

Updated checks on freeze points:

>>> import numpy as np
>>> from scp.ethylene_glycol import EthyleneGlycol
>>> fluid = EthyleneGlycol(0.1)
>>> x = np.array([0.0, 0.05, 0.1, 0.1395, 0.20, 0.235, 0.30, 0.305, 0.362, 0.40, 0.455, 0.50, 0.526, 0.60])
>>> np.array([fluid.freeze_point(xi) for xi in x])
array([-2.75567402e-04, -1.58254148e+00, -3.35672402e+00, -4.97354793e+00,
       -7.94856362e+00, -1.00023118e+01, -1.45759668e+01, -1.49720678e+01,
       -1.99688636e+01, -2.38130870e+01, -3.01372382e+01, -3.59943788e+01,
       -3.96597622e+01, -5.12009178e+01])
>>> import numpy as np
>>> from scp.propylene_glycol import PropyleneGlycol
>>> fluid = PropyleneGlycol(0.1)
>>> x = np.array([0.05, 0.10, 0.156, 0.20, 0.254, 0.30, 0.329, 0.3939, 0.40, 0.4882, 0.50, 0.55, 0.60])
>>> np.array([fluid.freeze_point(xi) for xi in x])
array([ -1.2101421 ,  -2.86882049,  -5.13501127,  -7.17467035,
       -10.01104971, -12.78966304, -14.76226218, -20.00375927,
       -20.56777711, -30.54964945, -32.19288083, -40.15792234,
       -50.00293223])
>>> import numpy as np
>>> from scp.ethyl_alcohol import EthylAlcohol
>>> fluid = EthylAlcohol(0.1)
>>> x = np.array([0.05, 0.10, 0.111, 0.15, 0.187, 0.20, 0.245, 0.25, 0.297, 0.30, 0.40, 0.407, 0.50, 0.531, 0.60])
>>> np.array([fluid.freeze_point(xi) for xi in x])
array([ -2.03335185,  -4.38470066,  -4.98542992,  -7.39645681,
       -10.09044227, -11.12449924, -14.99701302, -15.45057276,
       -19.85789073, -20.14491312, -29.53754175, -30.15661898,
       -37.61383893, -39.83814713, -44.90911286])
>>> import numpy as np
>>> from scp.methyl_alcohol import MethylAlcohol
>>> fluid = MethylAlcohol(0.1)
>>> x = np.array([0.05, 0.0786, 0.10, 0.1436, 0.15, 0.1995, 0.2487, 0.30, 0.3348, 0.40, 0.4095, 0.50, 0.60])
>>> np.array([fluid.freeze_point(xi) for xi in x])
array([ -3.00148741,  -4.96442293,  -6.53962472, -10.02342682,
       -10.56568373, -15.03146795, -19.96732383, -25.68455881,
       -29.92129695, -38.70339538, -40.0793146 , -54.46679364,
       -73.00626984])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants