mirror of
https://github.com/CoolProp/CoolProp.git
synced 2026-01-09 14:08:08 -05:00
351 lines
13 KiB
Python
351 lines
13 KiB
Python
from __future__ import division
|
|
import os
|
|
import glob
|
|
import string
|
|
import random
|
|
import numpy as np
|
|
from scipy.odr import *
|
|
import json
|
|
import matplotlib.pyplot as plt
|
|
import CoolProp
|
|
import CoolProp.CoolProp as CP
|
|
|
|
LIBRARY = [i / 6.0 for i in range(1, 151)] + [0.35 + i / 2000 for i in range(1, 100)] + [0.05 + 0.001 * i for i in range(1, 100)] + [i + 0.5 for i in range(10)]
|
|
#LIBRARY = [i/1000 for i in range(1,20000)]
|
|
|
|
|
|
class Sample(object):
|
|
def __init__(self, v):
|
|
self.v = v
|
|
|
|
|
|
class GeneticAncillaryFitter(object):
|
|
def __init__(self,
|
|
num_samples=600, # Have this many chromos in the sample group
|
|
num_selected=60, # Have this many chromos in the selected group
|
|
mutation_factor=2, # Randomly mutate 1/n of the chromosomes
|
|
num_powers=5, # How many powers in the fit
|
|
Ref='R407C',
|
|
value='rhoV',
|
|
addTr=True,
|
|
values=None,
|
|
Tlims=None
|
|
):
|
|
self.num_samples = num_samples
|
|
self.num_selected = num_selected
|
|
self.mutation_factor = mutation_factor
|
|
self.num_powers = num_powers
|
|
self.addTr = addTr
|
|
self.value = value
|
|
self.Ref = Ref
|
|
|
|
# Thermodynamics
|
|
from CoolProp.CoolProp import PropsSI
|
|
|
|
if values is None:
|
|
self.Tc = PropsSI(Ref, 'Tcrit')
|
|
self.pc = PropsSI(Ref, 'pcrit')
|
|
self.rhoc = PropsSI(Ref, 'rhomolar_critical')
|
|
self.Tmin = PropsSI(Ref, 'Tmin')
|
|
if Tlims is None:
|
|
self.T = np.append(np.linspace(self.Tmin + 1e-14, self.Tc - 1, 150), np.logspace(np.log10(self.Tc - 1), np.log10(self.Tc) - 1e-15, 40))
|
|
else:
|
|
self.T = np.linspace(Tlims[0], Tlims[1])
|
|
self.pL = np.array(PropsSI('P', 'T', self.T, 'Q', [0] * len(self.T), Ref))
|
|
self.pV = np.array(PropsSI('P', 'T', self.T, 'Q', [1] * len(self.T), Ref))
|
|
self.rhoL = PropsSI('Dmolar', 'T', self.T, 'Q', [0] * len(self.T), Ref)
|
|
self.rhoV = PropsSI('Dmolar', 'T', self.T, 'Q', [1] * len(self.T), Ref)
|
|
else:
|
|
self.Tc = values['Tcrit']
|
|
self.pc = values['pcrit']
|
|
self.rhoc = values['rhocrit']
|
|
self.Tmin = values['Tmin']
|
|
self.T = np.array(values['T'])
|
|
self.p = np.array(values['p'])
|
|
self.pL = np.array(values['p'])
|
|
self.pV = np.array(values['p'])
|
|
self.rhoL = np.array(values['rhoL'])
|
|
self.rhoV = np.array(values['rhoV'])
|
|
|
|
self.logpLpc = (np.log(self.pL) - np.log(self.pc))
|
|
self.logpVpc = (np.log(self.pV) - np.log(self.pc))
|
|
self.rhoLrhoc = np.array(self.rhoL) / self.rhoc
|
|
self.rhoVrhoc = np.array(self.rhoV) / self.rhoc
|
|
self.logrhoLrhoc = np.log(self.rhoL) - np.log(self.rhoc)
|
|
self.logrhoVrhoc = np.log(self.rhoV) - np.log(self.rhoc)
|
|
|
|
self.x = 1.0 - self.T / self.Tc
|
|
|
|
MM = PropsSI(Ref, 'molemass')
|
|
self.T_r = self.Tc
|
|
|
|
if self.value == 'pL':
|
|
self.LHS = self.logpLpc.copy()
|
|
self.EOS_value = self.pL.copy()
|
|
if self.addTr == False:
|
|
self.description = "p' = pc*exp(sum(n_i*theta^t_i))"
|
|
else:
|
|
self.description = "p' = pc*exp(Tc/T*sum(n_i*theta^t_i))"
|
|
self.reducing_value = self.pc
|
|
elif self.value == 'pV':
|
|
self.LHS = self.logpVpc.copy()
|
|
self.EOS_value = self.pV.copy()
|
|
if self.addTr == False:
|
|
self.description = "p'' = pc*exp(sum(n_i*theta^t_i))"
|
|
else:
|
|
self.description = "p'' = pc*exp(Tc/T*sum(n_i*theta^t_i))"
|
|
self.reducing_value = self.pc
|
|
elif self.value == 'rhoL':
|
|
self.LHS = self.logrhoLrhoc.copy()
|
|
self.EOS_value = self.rhoL
|
|
if self.addTr == False:
|
|
self.description = "rho' = rhoc*exp(sum(n_i*theta^t_i))"
|
|
else:
|
|
self.description = "rho' = rhoc*exp(Tc/T*sum(n_i*theta^t_i))"
|
|
self.reducing_value = self.rhoc
|
|
elif self.value == 'rhoV':
|
|
self.LHS = self.logrhoVrhoc.copy()
|
|
self.EOS_value = self.rhoV
|
|
if self.addTr == False:
|
|
self.description = "rho'' = rhoc*exp(sum(n_i*theta^t_i))"
|
|
else:
|
|
self.description = "rho'' = rhoc*exp(Tc/T*sum(n_i*theta^t_i))"
|
|
self.reducing_value = self.rhoc
|
|
elif self.value == 'rhoLnoexp':
|
|
self.LHS = (self.rhoLrhoc - 1).copy()
|
|
self.EOS_value = self.rhoL
|
|
self.description = "rho' = rhoc*(1+sum(n_i*theta^t_i))"
|
|
self.reducing_value = self.rhoc
|
|
else:
|
|
raise ValueError
|
|
|
|
if self.value == 'rhoLnoexp' and self.addTr:
|
|
raise ValueError('Invalid combination')
|
|
|
|
if self.addTr:
|
|
self.LHS *= self.T / self.Tc
|
|
|
|
def generate_random_chromosomes(self,):
|
|
'''
|
|
Create a list of random chromosomes to seed our algorithm.
|
|
'''
|
|
chromos = []
|
|
while len(chromos) < self.num_samples:
|
|
chromos.append(Sample(sorted(random.sample(LIBRARY, self.num_powers))))
|
|
return chromos
|
|
|
|
def fitness(self, chromo):
|
|
'''
|
|
Fitness of a chromo is the sum of the squares of the error of the correlation
|
|
'''
|
|
|
|
# theta^t where the i-th row is equal to theta^t[i]
|
|
# We need these terms later on to build the A and b matrices
|
|
theta_t = (self.x.reshape(-1, 1)**chromo.v).T
|
|
|
|
# TODO: more numpy broadcasting should speed this up even more
|
|
# Another few times faster ought to be possible
|
|
I = len(chromo.v)
|
|
A = np.zeros((I, I))
|
|
b = np.zeros((I, 1))
|
|
for i in range(I):
|
|
for j in range(I):
|
|
A[i, j] = np.sum(theta_t[i] * theta_t[j])
|
|
b[i] = np.sum(theta_t[i] * self.LHS)
|
|
|
|
# If you end up with a singular matrix, quit this run
|
|
try:
|
|
n = np.linalg.solve(A, b).T
|
|
except np.linalg.linalg.LinAlgError as E:
|
|
chromo.fitness = 1e99
|
|
return
|
|
|
|
chromo.beta = n
|
|
RHS = np.sum(n * self.x.reshape(-1, 1)**chromo.v, axis=1)
|
|
|
|
if self.addTr:
|
|
RHS *= self.Tc / self.T
|
|
|
|
if self.value in ['pL', 'pV']:
|
|
fit_value = np.exp(RHS) * self.pc
|
|
elif self.value in ['rhoL', 'rhoV']:
|
|
fit_value = np.exp(RHS) * self.rhoc
|
|
elif self.value == 'rhoLnoexp':
|
|
fit_value = self.rhoc * (1 + RHS)
|
|
else:
|
|
raise ValueError
|
|
|
|
max_abserror = np.max(np.abs((fit_value / self.EOS_value) - 1) * 100)
|
|
|
|
chromo.fitness = max_abserror
|
|
chromo.fit_value = fit_value
|
|
chromo.max_abserror = max_abserror
|
|
|
|
return chromo.fitness
|
|
|
|
def tourny_select_chromo(self, samples):
|
|
'''
|
|
Randomly select two chromosomes from the samples, then return the one
|
|
with the best fitness.
|
|
'''
|
|
a = random.choice(samples)
|
|
b = random.choice(samples)
|
|
if a.fitness < b.fitness:
|
|
return a
|
|
else:
|
|
return b
|
|
|
|
def breed(self, a, b):
|
|
'''
|
|
Breed two chromosomes by splicing them in a random spot and combining
|
|
them together to form two new chromos.
|
|
'''
|
|
splice_pos = random.randrange(len(a.v))
|
|
new_a = a.v[:splice_pos] + b.v[splice_pos:]
|
|
new_b = b.v[:splice_pos] + a.v[splice_pos:]
|
|
return Sample(sorted(new_a)), Sample(sorted(new_b))
|
|
|
|
def mutate(self, chromo):
|
|
'''
|
|
Mutate a chromosome by changing one of the parameters, but only if it improves the fitness
|
|
'''
|
|
v = chromo.v
|
|
if hasattr(chromo, 'fitness'):
|
|
old_fitness = chromo.fitness
|
|
else:
|
|
old_fitness = self.fitness(chromo)
|
|
|
|
for i in range(10):
|
|
pos = random.randrange(len(chromo.v))
|
|
chromo.v[pos] = random.choice(LIBRARY)
|
|
new_fitness = self.fitness(chromo)
|
|
if new_fitness < old_fitness:
|
|
return chromo
|
|
else:
|
|
return Sample(sorted(v))
|
|
|
|
def run(self):
|
|
# Create a random sample of chromos
|
|
samples = self.generate_random_chromosomes()
|
|
|
|
# Calculate the fitness for the initial chromosomes
|
|
for chromo in samples:
|
|
self.fitness(chromo)
|
|
# print '#'
|
|
|
|
decorated = sorted([(sample.fitness, sample) for sample in samples])
|
|
samples = [s for sv, s in decorated]
|
|
values = [sv for sv, s in decorated]
|
|
plt.plot(values[0:len(values) // 2])
|
|
plt.close()
|
|
|
|
# Main loop: each generation select a subset of the sample and breed from
|
|
# them.
|
|
generation = -1
|
|
while generation < 0 or samples[0].fitness > 0.02 or (generation < 3 and generation < 15):
|
|
generation += 1
|
|
|
|
# Generate the selected group from sample- take the top 10% of samples
|
|
# and tourny select to generate the rest of selected.
|
|
ten_percent = int(len(samples) * .1)
|
|
selected = samples[:ten_percent]
|
|
while len(selected) < self.num_selected:
|
|
selected.append(self.tourny_select_chromo(samples))
|
|
|
|
# Generate the solution group by breeding random chromos from selected
|
|
solution = []
|
|
while len(solution) < self.num_samples:
|
|
solution.extend(self.breed(random.choice(selected),
|
|
random.choice(selected)))
|
|
|
|
# Apply a mutation to a subset of the solution set
|
|
mutate_indices = random.sample(range(len(solution)), len(solution) // self.mutation_factor)
|
|
for i in mutate_indices:
|
|
solution[i] = self.mutate(solution[i])
|
|
|
|
for chromo in solution:
|
|
self.fitness(chromo)
|
|
# print '#'
|
|
|
|
decorated = sorted([(sample.fitness, sample) for sample in solution])
|
|
samples = [s for sv, s in decorated]
|
|
|
|
# print '------------------ Top 10 values ---------------'
|
|
# for sample in samples[0:10]:
|
|
# print sample.v, sample.fitness, sample.max_abserror
|
|
|
|
# print '// Max error is ',samples[0].max_abserror,'% between',np.min(self.T),'and',np.max(self.T),'K'
|
|
# print str(samples[0].v), samples[0].beta.tolist()
|
|
|
|
# Print useful stats about this generation
|
|
(min, median, max) = [samples[0].fitness, samples[len(samples) // 2].fitness, samples[-1].fitness]
|
|
# print("{0} best value: {1}. fitness: best {2}, median {3}, worst {4}".format(generation, samples[0].v, min, median, max))
|
|
|
|
# If the string representations of all the chromosomes are the same, stop
|
|
if len(set([str(s.v) for s in samples[0:5]])) == 1:
|
|
break
|
|
|
|
self.fitness(samples[0])
|
|
|
|
print(self.value)
|
|
print('// Max error is ' + str(samples[0].max_abserror) + ' % between ' + str(np.min(self.T)) + ' and ' + str(np.max(self.T)) + ' K')
|
|
|
|
self.fit_value = samples[0].fit_value
|
|
|
|
j = dict()
|
|
j['n'] = samples[0].beta.squeeze().tolist()
|
|
j['t'] = samples[0].v
|
|
j['Tmin'] = np.min(self.T)
|
|
j['Tmax'] = np.max(self.T)
|
|
j['type'] = self.value
|
|
j['using_tau_r'] = self.addTr
|
|
j['reducing_value'] = self.reducing_value
|
|
j['T_r'] = self.Tc
|
|
|
|
# Informational, not used
|
|
j['max_abserror_percentage'] = samples[0].max_abserror
|
|
j['description'] = self.description
|
|
|
|
return j
|
|
|
|
|
|
def build_ancillaries(name, **kwargs):
|
|
|
|
j = dict()
|
|
j['ANCILLARIES'] = dict()
|
|
gaf = GeneticAncillaryFitter(Ref=name, value='pL', addTr=True, num_powers=6, **kwargs)
|
|
j['ANCILLARIES']['pL'] = gaf.run()
|
|
gaf = GeneticAncillaryFitter(Ref=name, value='pV', addTr=True, num_powers=6, **kwargs)
|
|
j['ANCILLARIES']['pV'] = gaf.run()
|
|
gaf = GeneticAncillaryFitter(Ref=name, value='rhoLnoexp', addTr=False, num_powers=6, **kwargs)
|
|
j['ANCILLARIES']['rhoL'] = gaf.run()
|
|
gaf = GeneticAncillaryFitter(Ref=name, value='rhoV', addTr=True, num_powers=6, **kwargs)
|
|
j['ANCILLARIES']['rhoV'] = gaf.run()
|
|
|
|
fp = open(os.path.join('ancillaries', name + '_anc.json'), 'w')
|
|
print(json.dumps(j, indent=2), file=fp)
|
|
fp.close()
|
|
|
|
|
|
def build_all_ancillaries():
|
|
for fluid in sorted(CoolProp.__fluids__):
|
|
print(fluid)
|
|
if fluid in ['SES36']:
|
|
build_ancillaries(fluid, Tlims=[CP.Props(fluid, 'Ttriple'), CP.Props(fluid, 'Tcrit') - 1])
|
|
elif fluid == 'R507A':
|
|
build_ancillaries(fluid, Tlims=[CP.Props(fluid, 'Ttriple'), CP.Props(fluid, 'Tcrit') - 0.1])
|
|
elif fluid == 'R407F':
|
|
build_ancillaries(fluid, Tlims=[CP.Props(fluid, 'Ttriple'), CP.Props(fluid, 'Tcrit') - 2])
|
|
else:
|
|
build_ancillaries(fluid)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
fluid = 'Methanol'
|
|
RPfluid = fluid
|
|
build_ancillaries(RPfluid, Tlims=[CP.PropsSI(fluid, 'Ttriple'), CP.PropsSI(fluid, 'Tcrit') - 0.01])
|
|
|
|
#~ build_all_ancillaries()
|
|
# inject_ancillaries()
|