55from pycycle .thermo .cea .species_data import Properties , janaf
66from pycycle .constants import AIR_FUEL_ELEMENTS , AIR_ELEMENTS
77from pycycle .flow_in import FlowIn
8+ from pycycle .elements .mix_ratio import MixRatio
89
910
10- class MixFlow (om .ExplicitComponent ):
11+ class MixImpulse (om .ExplicitComponent ):
1112 """
12- Mix two incoming flow streams
13- """
14-
15- def initialize (self ):
16- self .options .declare ('thermo_data' , default = janaf ,
17- desc = 'thermodynamic data set' , recordable = False )
18- self .options .declare ('Fl_I1_elements' , default = AIR_ELEMENTS ,
19- desc = 'set of elements present in the flow' )
20- self .options .declare ('Fl_I2_elements' , default = AIR_FUEL_ELEMENTS ,
21- desc = 'set of elements present in the flow' )
13+ Compute the combined impulse of two streams
14+ """
2215
2316 def setup (self ):
2417
25- thermo_data = self .options ['thermo_data' ]
26-
2718
28- self .flow1_elements = self .options ['Fl_I1_elements' ]
29- flow1_thermo = Properties (thermo_data , init_reacts = self .flow1_elements )
30- n_flow1_prods = len (flow1_thermo .products )
31- self .flow1_wt_mole = flow1_thermo .wt_mole
32- self .add_input ('Fl_I1:tot:h' , val = 0.0 , units = 'J/kg' , desc = 'total enthalpy for flow 1' )
33- self .add_input ('Fl_I1:tot:n' , val = np .zeros (n_flow1_prods ), desc = 'species composition for flow 1' )
3419 self .add_input ('Fl_I1:stat:W' , val = 0.0 , units = 'kg/s' , desc = 'mass flow for flow 1' )
3520 self .add_input ('Fl_I1:stat:P' , val = 0.0 , units = 'Pa' , desc = 'static pressure for flow 1' )
3621 self .add_input ('Fl_I1:stat:V' , val = 0.0 , units = 'm/s' , desc = 'velocity for flow 1' )
3722 self .add_input ('Fl_I1:stat:area' , val = 0.0 , units = 'm**2' , desc = 'area for flow 1' )
3823
39- self .flow2_elements = self .options ['Fl_I2_elements' ]
40- flow2_thermo = Properties (thermo_data , init_reacts = self .flow2_elements )
41- n_flow2_prods = len (flow2_thermo .products )
42- self .flow2_wt_mole = flow2_thermo .wt_mole
43- self .aij = flow1_thermo .aij
4424
45- self .add_input ('Fl_I2:tot:h' , val = 0.0 , units = 'J/kg' , desc = 'total enthalpy for flow 2' )
46- self .add_input ('Fl_I2:tot:n' , val = np .zeros (n_flow2_prods ), desc = 'species composition for flow 2' )
4725 self .add_input ('Fl_I2:stat:W' , val = 0.0 , units = 'kg/s' , desc = 'mass flow for flow 2' )
4826 self .add_input ('Fl_I2:stat:P' , val = 0.0 , units = 'Pa' , desc = 'static pressure for flow 2' )
4927 self .add_input ('Fl_I2:stat:V' , val = 0.0 , units = 'm/s' , desc = 'velocity for flow 2' )
5028 self .add_input ('Fl_I2:stat:area' , val = 0.0 , units = 'm**2' , desc = 'area for flow 2' )
5129
52- self .add_output ('ht_mix' , val = 0.0 , units = 'J/kg' , desc = 'total enthalpy out' )
53- self .add_output ('n_mix' , val = np .zeros (n_flow1_prods ), desc = 'species composition for flow out' )
54- self .add_output ('W_mix' , val = 0.0 , units = 'kg/s' , desc = 'mass flow for flow out' )
5530 self .add_output ('impulse_mix' , val = 0. , units = 'N' , desc = 'impulse of the outgoing flow' )
56- self .add_output ('b0_mix' , val = flow1_thermo .b0 )
5731
5832
59- ################################################################################
60- # we assume that you are always mixing 2 into 1
61- # so, the elements in 2 must be a subset of the elements in 1!!!
62- ################################################################################
63- # TODO: raise error if this is not True
64-
65- # create mapping for main and bleed flow
66- # self.flow_1_idx_map = {prod: i for i, prod in enumerate(flow1_thermo.products)}
67- self .flow_1_idx_map = {prod : i for i , prod in enumerate (flow1_thermo .products )}
68-
69- self .mix_mat = np .zeros ((n_flow2_prods , n_flow1_prods ), dtype = int )
70- for i , prod in enumerate (flow2_thermo .products ):
71- j = self .flow_1_idx_map [prod ]
72- self .mix_mat [i ,j ] = 1
73-
74- # need to transpose so it maps 2 into 1
75- self .mix_mat = self .mix_mat .T
76-
77- self .declare_partials ('ht_mix' , ['Fl_I1:tot:h' , 'Fl_I2:tot:h' , 'Fl_I1:stat:W' , 'Fl_I2:stat:W' ])
78- self .declare_partials ('W_mix' , ['Fl_I1:stat:W' , 'Fl_I2:stat:W' ], val = 1. ) # linear!
79- self .declare_partials ('n_mix' , ['Fl_I1:tot:n' , 'Fl_I2:tot:n' ])
80- self .declare_partials ('n_mix' , ['Fl_I1:stat:W' , 'Fl_I2:stat:W' ])
33+
8134 self .declare_partials ('impulse_mix' , ['Fl_I1:stat:P' , 'Fl_I1:stat:area' , 'Fl_I1:stat:W' , 'Fl_I1:stat:V' ,
8235 'Fl_I2:stat:P' , 'Fl_I2:stat:area' , 'Fl_I2:stat:W' , 'Fl_I2:stat:V' ])
8336
84- self .declare_partials ('b0_mix' , ['Fl_I1:tot:n' , 'Fl_I2:tot:n' ])
85- self .declare_partials ('b0_mix' , ['Fl_I1:stat:W' , 'Fl_I2:stat:W' ])
86-
8737 self .set_check_partial_options ('*' , method = 'cs' )
8838
8939
9040 def compute (self , inputs , outputs ):
9141
92- W1 = inputs ['Fl_I1:stat:W' ]
93- W2 = inputs ['Fl_I2:stat:W' ]
94- Wmix = outputs ['W_mix' ] = W1 + W2
95- outputs ['ht_mix' ] = (W1 * inputs ['Fl_I1:tot:h' ] + W2 * inputs ['Fl_I2:tot:h' ])/ Wmix
96-
9742 outputs ['impulse_mix' ] = (inputs ['Fl_I1:stat:P' ]* inputs ['Fl_I1:stat:area' ] + inputs ['Fl_I1:stat:W' ]* inputs ['Fl_I1:stat:V' ]) + \
9843 (inputs ['Fl_I2:stat:P' ]* inputs ['Fl_I2:stat:area' ] + inputs ['Fl_I2:stat:W' ]* inputs ['Fl_I2:stat:V' ])
9944
100- ######################################################
101- # Begin the mass averaged composition calculations:
102- ######################################################
103- # convert the incoming flow composition vectors into mass units
104- Fl_I1_n_mass = inputs ['Fl_I1:tot:n' ] * self .flow1_wt_mole
105- Fl_I2_n_mass = inputs ['Fl_I2:tot:n' ] * self .flow2_wt_mole
106-
107- # normalize the mass arrays to 1 kg each
108- Fl_I1_n_mass /= np .sum (Fl_I1_n_mass )
109- Fl_I2_n_mass /= np .sum (Fl_I2_n_mass )
110-
111- # scale by the incoming mass flow rates
112- Fl_I1_n_mass *= W1
113- Fl_I2_n_mass *= W2
114-
115- # sum the flow components together and normalize it down to 1 Kg
116- Fl_O_n_mass = (self .mix_mat .dot (Fl_I2_n_mass ) + Fl_I1_n_mass )/ Wmix
117-
118- # convert back to molar units
119- outputs ['n_mix' ] = Fl_O_n_mass / self .flow1_wt_mole
120- outputs ['b0_mix' ] = np .sum (self .aij * outputs ['n_mix' ], axis = 1 )
12145
12246 def compute_partials (self , inputs , J ):
12347
124- ht1 = inputs ['Fl_I1:tot:h' ]
125- ht2 = inputs ['Fl_I2:tot:h' ]
126- W1 = inputs ['Fl_I1:stat:W' ]
127- W2 = inputs ['Fl_I2:stat:W' ]
128- Wmix = W1 + W2
129- J ['ht_mix' , 'Fl_I1:stat:W' ] = W2 * (ht1 - ht2 )/ Wmix ** 2
130- J ['ht_mix' , 'Fl_I2:stat:W' ] = W1 * (ht2 - ht1 )/ Wmix ** 2
131- J ['ht_mix' , 'Fl_I1:tot:h' ] = W1 / Wmix
132- J ['ht_mix' , 'Fl_I2:tot:h' ] = W2 / Wmix
133-
13448 J ['impulse_mix' , 'Fl_I1:stat:P' ] = inputs ['Fl_I1:stat:area' ]
13549 J ['impulse_mix' , 'Fl_I1:stat:area' ] = inputs ['Fl_I1:stat:P' ]
13650 J ['impulse_mix' , 'Fl_I1:stat:W' ] = inputs ['Fl_I1:stat:V' ]
@@ -142,46 +56,6 @@ def compute_partials(self, inputs, J):
14256 J ['impulse_mix' , 'Fl_I2:stat:V' ] = inputs ['Fl_I2:stat:W' ]
14357
14458
145- # composition derivatives
146- n1_mass = inputs ['Fl_I1:tot:n' ] * self .flow1_wt_mole
147- n2_mass = inputs ['Fl_I2:tot:n' ] * self .flow2_wt_mole
148-
149- n1_mass_hat = n1_mass / np .sum (n1_mass )
150- n2_mass_hat = self .mix_mat .dot (n2_mass / np .sum (n2_mass ))
151-
152- dnout_mole_q_dnout_mass = np .diag (1 / self .flow1_wt_mole )
153-
154- dnout_mass_q_dn1hat_mass = W1 / Wmix
155- dnout_mass_q_dn2hat_mass = self .mix_mat * W2 / Wmix
156-
157- n_n1 = len (n1_mass )
158- sum_n1_mass = np .sum (n1_mass )
159- dn1hat_mass_q_dn1_mass = - np .tile (n1_mass , (n_n1 ,1 )).T / sum_n1_mass ** 2
160- dn1hat_mass_q_dn1_mass += np .eye (n_n1 )/ sum_n1_mass # diagonal term
161-
162-
163- n_n2 = len (n2_mass )
164- sum_n2_mass = np .sum (n2_mass )
165- dn2hat_mass_q_dn2_mass = - np .tile (n2_mass , (n_n2 ,1 )).T / sum_n2_mass ** 2
166- dn2hat_mass_q_dn2_mass += np .eye (n_n2 )/ sum_n2_mass # diagonal term
167-
168- dn1_mass_q_dn1_mole = np .diag (self .flow1_wt_mole )
169- dn2_mass_q_dn2_mole = np .diag (self .flow2_wt_mole )
170-
171- J ['n_mix' , 'Fl_I1:tot:n' ] = dnout_mole_q_dnout_mass .dot (dnout_mass_q_dn1hat_mass * (dn1hat_mass_q_dn1_mass .dot (dn1_mass_q_dn1_mole )))
172- J ['n_mix' , 'Fl_I2:tot:n' ] = dnout_mole_q_dnout_mass .dot (dnout_mass_q_dn2hat_mass .dot (dn2hat_mass_q_dn2_mass .dot (dn2_mass_q_dn2_mole )))
173-
174- dnout_mass_q_dW1 = W2 * (n1_mass_hat - n2_mass_hat )/ Wmix ** 2
175- dnout_mass_q_dW2 = W1 * (n2_mass_hat - n1_mass_hat )/ Wmix ** 2
176- J ['n_mix' , 'Fl_I1:stat:W' ] = dnout_mole_q_dnout_mass .dot (dnout_mass_q_dW1 )
177- J ['n_mix' , 'Fl_I2:stat:W' ] = dnout_mole_q_dnout_mass .dot (dnout_mass_q_dW2 )
178-
179- J ['b0_mix' , 'Fl_I1:tot:n' ] = np .matmul (self .aij ,J ['n_mix' ,'Fl_I1:tot:n' ])
180- J ['b0_mix' , 'Fl_I2:tot:n' ] = np .matmul (self .aij ,J ['n_mix' ,'Fl_I2:tot:n' ])
181- J ['b0_mix' , 'Fl_I1:stat:W' ] = np .matmul (self .aij ,J ['n_mix' , 'Fl_I1:stat:W' ])
182- J ['b0_mix' , 'Fl_I2:stat:W' ] = np .matmul (self .aij ,J ['n_mix' , 'Fl_I2:stat:W' ])
183-
184-
18559class AreaSum (om .ExplicitComponent ):
18660
18761 def setup (self ):
@@ -283,13 +157,13 @@ def setup(self):
283157 thermo_data = self .options ['thermo_data' ]
284158
285159 flow1_elements = self .options ['Fl_I1_elements' ]
286- flow1_thermo = Properties (thermo_data , init_reacts = flow1_elements )
160+ flow1_thermo = Properties (thermo_data , init_elements = flow1_elements )
287161 n_flow1_prods = flow1_thermo .num_prod
288162 in_flow = FlowIn (fl_name = 'Fl_I1' , num_prods = n_flow1_prods , num_elements = flow1_thermo .num_element )
289163 self .add_subsystem ('in_flow1' , in_flow , promotes = ['Fl_I1:*' ])
290164
291165 flow2_elements = self .options ['Fl_I2_elements' ]
292- flow2_thermo = Properties (thermo_data , init_reacts = flow2_elements )
166+ flow2_thermo = Properties (thermo_data , init_elements = flow2_elements )
293167 n_flow2_prods = flow2_thermo .num_prod
294168 in_flow = FlowIn (fl_name = 'Fl_I2' , num_prods = n_flow2_prods , num_elements = flow2_thermo .num_element )
295169 self .add_subsystem ('in_flow2' , in_flow , promotes = ['Fl_I2:*' ])
@@ -358,18 +232,20 @@ def setup(self):
358232 promotes_inputs = [('Pt1' , 'Fl_I1:tot:P' ), ('Pt2' , 'Fl_I2:tot:P' )],
359233 promotes_outputs = ['ER' ])
360234
361- mix_flow = MixFlow (thermo_data = thermo_data ,
362- Fl_I1_elements = self .options ['Fl_I1_elements' ],
363- Fl_I2_elements = self .options ['Fl_I2_elements' ])
235+ self .add_subsystem ('mix_flow' , MixRatio (mix_thermo_data = thermo_data , mix_mode = 'flow' , mix_names = 'mix' ,
236+ inflow_elements = flow1_elements , mix_elements = flow2_elements ),
237+ promotes_inputs = [('Fl_I:stat:W' , 'Fl_I1:stat:W' ), ('Fl_I:tot:b0' , 'Fl_I1:tot:b0' ), ('Fl_I:tot:h' , 'Fl_I1:tot:h' ),
238+ ('mix:W' , 'Fl_I2:stat:W' ), ('mix:b0' , 'Fl_I2:tot:b0' ), ('mix:h' , 'Fl_I2:tot:h' )])
239+
364240 if self .options ['designed_stream' ] == 1 :
365- self .add_subsystem ('mix_flow ' , mix_flow ,
366- promotes_inputs = ['Fl_I1:tot:h' , 'Fl_I1:tot:n' , ('Fl_I1:stat:W' ,'Fl_I1_calc:stat:W' ), ('Fl_I1:stat:P' ,'Fl_I1_calc:stat:P' ),
241+ self .add_subsystem ('impulse_mix ' , MixImpulse () ,
242+ promotes_inputs = [('Fl_I1:stat:W' ,'Fl_I1_calc:stat:W' ), ('Fl_I1:stat:P' ,'Fl_I1_calc:stat:P' ),
367243 ('Fl_I1:stat:V' ,'Fl_I1_calc:stat:V' ), ('Fl_I1:stat:area' ,'Fl_I1_calc:stat:area' ),
368- 'Fl_I2:tot:h' , 'Fl_I2:tot:n' , 'Fl_I2: stat:W' , 'Fl_I2:stat:P' , 'Fl_I2:stat:V' , 'Fl_I2:stat:area' ])
244+ 'Fl_I2:stat:W' , 'Fl_I2:stat:P' , 'Fl_I2:stat:V' , 'Fl_I2:stat:area' ])
369245 else :
370- self .add_subsystem ('mix_flow ' , mix_flow ,
371- promotes_inputs = ['Fl_I1:tot:h' , 'Fl_I1:tot:n' , 'Fl_I1: stat:W' , 'Fl_I1:stat:P' , 'Fl_I1:stat:V' , 'Fl_I1:stat:area' ,
372- 'Fl_I2:tot:h' , 'Fl_I2:tot:n' , ('Fl_I2:stat:W' ,'Fl_I2_calc:stat:W' ), ('Fl_I2:stat:P' ,'Fl_I2_calc:stat:P' ),
246+ self .add_subsystem ('impulse_mix ' , MixImpulse () ,
247+ promotes_inputs = ['Fl_I1:stat:W' , 'Fl_I1:stat:P' , 'Fl_I1:stat:V' , 'Fl_I1:stat:area' ,
248+ ('Fl_I2:stat:W' ,'Fl_I2_calc:stat:W' ), ('Fl_I2:stat:P' ,'Fl_I2_calc:stat:P' ),
373249 ('Fl_I2:stat:V' ,'Fl_I2_calc:stat:V' ), ('Fl_I2:stat:area' ,'Fl_I2_calc:stat:area' )])
374250
375251
@@ -393,19 +269,19 @@ def setup(self):
393269 thermo_kwargs = {'elements' :self .options ['Fl_I1_elements' ],
394270 'spec' :thermo_data })
395271 conv .add_subsystem ('out_tot' , out_tot , promotes_outputs = ['Fl_O:tot:*' ])
396- self .connect ('mix_flow.b0_mix ' , 'out_tot.b0' )
397- self .connect ('mix_flow.ht_mix ' , 'out_tot.h' )
272+ self .connect ('mix_flow.b0_out ' , 'out_tot.b0' )
273+ self .connect ('mix_flow.mass_avg_h ' , 'out_tot.h' )
398274 # note: gets Pt from the balance comp
399275
400276 out_stat = Thermo (mode = 'static_A' , fl_name = 'Fl_O:stat' ,
401277 method = 'CEA' ,
402278 thermo_kwargs = {'elements' :self .options ['Fl_I1_elements' ],
403279 'spec' :thermo_data })
404280 conv .add_subsystem ('out_stat' , out_stat , promotes_outputs = ['Fl_O:stat:*' ], promotes_inputs = ['area' , ])
405- self .connect ('mix_flow.b0_mix ' , 'out_stat.b0' )
406- self .connect ('mix_flow.W_mix ' ,'out_stat.W' )
281+ self .connect ('mix_flow.b0_out ' , 'out_stat.b0' )
282+ self .connect ('mix_flow.Wout ' ,'out_stat.W' )
407283 conv .connect ('Fl_O:tot:S' , 'out_stat.S' )
408- self .connect ('mix_flow.ht_mix ' , 'out_stat.ht' )
284+ self .connect ('mix_flow.mass_avg_h ' , 'out_stat.ht' )
409285 conv .connect ('Fl_O:tot:P' , 'out_stat.guess:Pt' )
410286 conv .connect ('Fl_O:tot:gamma' , 'out_stat.guess:gamt' )
411287
@@ -419,5 +295,5 @@ def setup(self):
419295 balance .add_balance ('P_tot' , val = 100 , units = 'psi' , eq_units = 'N' , lower = 1e-3 , upper = 10000 )
420296 conv .connect ('balance.P_tot' , 'out_tot.P' )
421297 conv .connect ('imp_out.impulse' , 'balance.lhs:P_tot' )
422- self .connect ('mix_flow .impulse_mix' , 'balance.rhs:P_tot' ) #note that this connection comes from outside the convergence group
298+ self .connect ('impulse_mix .impulse_mix' , 'balance.rhs:P_tot' ) #note that this connection comes from outside the convergence group
423299
0 commit comments