turbo-design 1.3.1__tar.gz → 1.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of turbo-design might be problematic. Click here for more details.

Files changed (33) hide show
  1. {turbo_design-1.3.1 → turbo_design-1.3.3}/PKG-INFO +3 -2
  2. {turbo_design-1.3.1 → turbo_design-1.3.3}/pyproject.toml +1 -1
  3. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/bladerow.py +1 -0
  4. turbo_design-1.3.3/turbodesign/radeq.py +163 -0
  5. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/solve_radeq.py +1 -1
  6. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/spool.py +1 -0
  7. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/td_math.py +5 -4
  8. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/turbinespool.py +65 -69
  9. turbo_design-1.3.1/turbodesign/radeq.py +0 -286
  10. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/__init__.py +0 -0
  11. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/arrayfuncs.py +0 -0
  12. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/cantera_gas/co2.yaml +0 -0
  13. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/compressorspool.py +0 -0
  14. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/coolant.py +0 -0
  15. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/enums.py +0 -0
  16. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/inlet.py +0 -0
  17. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/isentropic.py +0 -0
  18. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/__init__.py +0 -0
  19. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/compressor/__init__.py +0 -0
  20. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/losstype.py +0 -0
  21. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/TD2.py +0 -0
  22. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/__init__.py +0 -0
  23. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/ainleymathieson.py +0 -0
  24. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/craigcox.py +0 -0
  25. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/fixedefficiency.py +0 -0
  26. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/fixedpressureloss.py +0 -0
  27. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/kackerokapuu.py +0 -0
  28. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/loss/turbine/traupel.py +0 -0
  29. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/lossinterp.py +0 -0
  30. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/outlet.py +0 -0
  31. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/passage.py +0 -0
  32. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/rotor.py +0 -0
  33. {turbo_design-1.3.1 → turbo_design-1.3.3}/turbodesign/stage.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: turbo-design
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: TurboDesign is a library used to design turbines and compressors using radial equilibrium.
5
5
  Author: Paht Juangphanich
6
6
  Author-email: paht.juangphanich@nasa.gov
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Requires-Dist: cantera
14
15
  Requires-Dist: findiff
15
16
  Requires-Dist: matplotlib
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "turbo-design"
3
- version = "1.3.1"
3
+ version = "1.3.3"
4
4
  description = "TurboDesign is a library used to design turbines and compressors using radial equilibrium."
5
5
  authors = ["Paht Juangphanich <paht.juangphanich@nasa.gov>"]
6
6
  packages = [
@@ -14,6 +14,7 @@ from .passage import Passage
14
14
 
15
15
 
16
16
  class BladeRow:
17
+ id:int = 0
17
18
  stage_id:int = 0
18
19
  row_type: RowType = RowType.Stator
19
20
  loss_function:LossBaseClass
@@ -0,0 +1,163 @@
1
+ from scipy.interpolate import interp1d,PchipInterpolator
2
+ from scipy.integrate import solve_ivp
3
+ import numpy as np
4
+ import numdifftools as nd
5
+ from .bladerow import BladeRow
6
+ from .enums import RowType
7
+ import math
8
+
9
+ def radeq(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None) -> BladeRow:
10
+ """Solves the radial equilibrium equation for axial machines and returns the convergence.
11
+
12
+ Note:
13
+ This function will give you T0, P0, Vm as a function of the radius.
14
+
15
+ Args:
16
+ row (BladeRow): Current row
17
+ upstream (BladeRow): Previous row
18
+ downstream (BladeRow): Next row
19
+
20
+ Returns:
21
+ BladeRow: current row with T0, P0, and Vm calculated
22
+ """
23
+ row_radius = row.r # Use these for gradient
24
+ up_radius = upstream.r
25
+
26
+ def ode_radeq_streamtube(r:np.ndarray,y:np.ndarray):
27
+ """Solves the radial equilibrium equation for a streamtube
28
+
29
+ Args:
30
+ r (np.ndarray): radius not as a percent
31
+ y (np.ndarray): Array containing [P0,Vt,VtU,T0]
32
+
33
+ """
34
+ P0 = y[0]
35
+ T0 = y[1]
36
+ Vm = y[2]
37
+ r = row.r.mean()+r
38
+ if r>row_radius[-1]:
39
+ return [0,0,0]
40
+ elif r<row_radius[0]:
41
+ return [0,0,0]
42
+
43
+ Cp = row.Cp
44
+ phi = interp1d(row_radius, row.phi)(r)
45
+ alpha = interp1d(row_radius, row.alpha2)(r)
46
+ T = interp1d(row_radius, row.T)(r)
47
+ P = interp1d(row_radius, row.P)(r)
48
+ rm = interp1d(row_radius, row.rm)(r)
49
+ rho = row.rho.mean()
50
+
51
+ if (row.row_type == RowType.Rotor):
52
+ omega = row.rpm*np.pi/30
53
+ U = omega*r
54
+ else:
55
+ omega = 0
56
+ U = 0
57
+ gamma = row.gamma
58
+
59
+ # Solve the Radial Equlibrium
60
+ Vt = Vm*np.tan(alpha)
61
+ Vr = Vm*np.sin(phi)
62
+ # Estimations
63
+ dVm_dr = float(interp1d(row_radius, np.gradient(row.Vm, row_radius))(r))
64
+ up_Vm = interp1d(row_radius, upstream.Vm)(r)
65
+
66
+ if downstream:
67
+ if downstream.row_type == RowType.Outlet:
68
+ down_Vm = Vm
69
+ else:
70
+ down_Vm = interp1d(row_radius, downstream.Vm)(r)
71
+ else:
72
+ down_Vm = Vm
73
+ up_m = interp1d(row_radius, upstream.m)(r)
74
+
75
+ # Get a rough guess of dVm/dm
76
+ if downstream!=None:
77
+ down_m = interp1d(row_radius, downstream.m)(r)
78
+ row_m = interp1d(row_radius, row.m)(r)
79
+ if down_m != row_m:
80
+ func_Vm_m = PchipInterpolator([up_m, row_m, down_m],[up_Vm, Vm, down_Vm])
81
+ else:
82
+ func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
83
+ else:
84
+ func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
85
+ dVm_dm = func_Vm_m.derivative()(row_m)
86
+
87
+ # Upstream
88
+ dT_dr = float(interp1d(row_radius, np.gradient(row.T,row_radius))(r))
89
+ dP_dr = float(interp1d(row_radius, np.gradient(row.P,row_radius))(r))
90
+ P = float(interp1d(row_radius, np.gradient(row.P,row_radius))(r))
91
+ dT0_dr = dT_dr + Vm/Cp * (1 + np.tan(alpha)**2)*dVm_dr
92
+ dP0_dr = dP_dr * (T0/T)**(gamma/(gamma-1)) + P*gamma/(gamma-1) * (T0/T)**(1/(gamma-1)) * (T*dT0_dr-T0*dT_dr)/T**2
93
+ # dT0_dr = float(interp1d(row.percent_hub_shroud, np.gradient(row.T0,row_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0])))
94
+ # dP0_dr = float(interp1d(row.percent_hub_shroud, np.gradient(row.P0,row_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0])))
95
+
96
+ C = (1 + np.tan(alpha)**2) * Vm**2/(2*Cp*T0)
97
+ if (C>1):
98
+ raise Exception(f"Invalid value of C {C:0.2f} which causes Vm to be nan.\nChange reduce alpha/beta for {row.row_type} {row.id}")
99
+ B = (1-C)**(gamma/(gamma-1))
100
+ A = -P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1)) * (1 + np.tan(alpha)**2)/(2*Cp)
101
+
102
+ eqn15_rhs = Vt**2/r - Vm**2/rm*np.cos(phi) - Vr*dVm_dm # right hand side of equation 15
103
+ eqn15_rhs_simple = Vt**2/r # right hand side of equation 15 simplified for axial machines
104
+
105
+ epsilon = 1e-10 # or another small threshold
106
+ if abs(rm) > epsilon:
107
+ dVm_dr = T0/(2*Vm*A) * (rho*eqn15_rhs - B*dP0_dr) + Vm/(2*T0) * dT0_dr # Eqn 21
108
+ else:
109
+ dVm_dr = T0/(2*Vm*A) * (rho*eqn15_rhs_simple - B*dP0_dr) + Vm/(2*T0) * dT0_dr # Eqn 21, simple
110
+ ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
111
+
112
+ return ydot
113
+
114
+ T0 = row.T0
115
+
116
+ P0 = row.P0
117
+ Vm = row.Vm
118
+
119
+ # Estimate the Vt based on a given turning angle
120
+ mean_radius = row_radius.mean()
121
+ tip_radius = row_radius.max()
122
+ hub_radius = row_radius.min()
123
+
124
+ T0m = interp1d(row.percent_hub_shroud,T0)(0.5);
125
+ P0m = interp1d(row.percent_hub_shroud,P0)(0.5); Vmm = interp1d(row.percent_hub_shroud,Vm)(0.5)
126
+ # We are solving for the values of these quantities at row exit
127
+ ics = np.array([P0m,T0m,Vmm])
128
+
129
+ # hub_to_tip = np.linspace(hub_radius,tip_radius)
130
+ # res = odeint(ode_radeq_streamtube, ics, hub_to_tip, tfirst=True)
131
+
132
+ # P0_new = interp1d(hub_to_tip,res[:,0])(row_radius)
133
+ # T0_new = interp1d(hub_to_tip,res[:,1])(row_radius)
134
+ # Vm_new = interp1d(hub_to_tip,res[:,2])(row_radius)
135
+ r_eval = row.r - mean_radius
136
+ # mean_radius_to_tip = np.linspace(0,tip_radius-mean_radius,len(row_radius)*5)
137
+ res1 = solve_ivp(ode_radeq_streamtube, t_span =[0, tip_radius-mean_radius], y0 = ics,
138
+ t_eval=np.linspace(0,tip_radius-mean_radius,len(row_radius)*2))
139
+
140
+ # mean_radius_to_hub = np.linspace(0,hub_radius-mean_radius,len(row_radius)*5)
141
+ res2 = solve_ivp(ode_radeq_streamtube, t_span = [0,hub_radius-mean_radius], y0 = ics,
142
+ t_eval=np.linspace(0,hub_radius-mean_radius,len(row_radius)*2))
143
+
144
+ mid_to_tip_vals = res1.y.transpose()
145
+ mid_to_tip_r = res1.t + mean_radius
146
+ mid_to_hub_vals = res2.y.transpose()
147
+ mid_to_hub_r = res2.t + mean_radius
148
+ mid_to_hub_vals = np.flipud(mid_to_hub_vals)
149
+ mid_to_hub_r = np.flipud(mid_to_hub_r)
150
+
151
+ hub_to_tip_vals = np.concatenate([mid_to_hub_vals[:-1,:],mid_to_tip_vals])
152
+
153
+ r = np.concatenate([mid_to_hub_r[:-1], mid_to_tip_r])
154
+
155
+ P0_new = interp1d(r,hub_to_tip_vals[:,0])(row_radius)
156
+ T0_new = interp1d(r,hub_to_tip_vals[:,1])(row_radius)
157
+ Vm_new = interp1d(r,hub_to_tip_vals[:,2])(row_radius)
158
+
159
+ row.P0 = P0_new
160
+ row.T0 = T0_new
161
+ row.Vm = Vm_new
162
+ return row
163
+
@@ -1,5 +1,5 @@
1
1
  from typing import List, Tuple
2
- from .radeq import radeq, radeq_normalized
2
+ from .radeq import radeq
3
3
  from .enums import LossType, RowType, PowerType, MassflowConstraint
4
4
  from .bladerow import BladeRow
5
5
  from .td_math import compute_gas_constants
@@ -69,6 +69,7 @@ class Spool:
69
69
  1. Conventional: Stator-Rotor-Stator-Rotor-etc. Set the RPM equally across all
70
70
  2. Counter Rotation: We either use the RPM already persecribed for each blade row.
71
71
  '''
72
+ self.blade_rows[i].id = i
72
73
  if (type(self.blade_rows[i]) != Inlet) and (type(self.blade_rows[i]) != Outlet):
73
74
  self.blade_rows[i].rpm = rpm
74
75
  self.blade_rows[i].axial_chord = self.blade_rows[i].location * self.passage.hub_length
@@ -132,7 +132,8 @@ def compute_power(row:BladeRow,upstream:BladeRow) -> None:
132
132
  a = np.sqrt(row.gamma*row.R*row.T_is)
133
133
  row.T0_is = row.T_is * (1+(row.gamma-1)/2*(row.V/a)**2)
134
134
 
135
- row.power = row.massflow[-1] * row.Cp * (upstream.T0.mean() - row.T0.mean())
135
+ row.power = row.massflow[-1] * (row.Cp * (upstream.T0 - row.T0)).mean()
136
+ # row.power = sum(v * w for v, w in zip(row.power[1:], np.diff(row.massflow))) # Massflow weighted average
136
137
  row.eta_static = row.power/ (row.massflow[-1]*row.Cp*(upstream.T0.mean()-row.T_is.mean()))
137
138
  row.eta_total = (upstream.T0.mean() - row.T0.mean()) / (upstream.T0.mean() - row.T0_is.mean())
138
139
  row.stage_loading = row.Cp*(upstream.T0.mean() - row.T0.mean())/row.U.mean()**2
@@ -296,7 +297,7 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
296
297
  row.P0R = upstream.P0R - row.Yp*(upstream.P0R-row.P)
297
298
 
298
299
  # Total Relative Temperature stays constant through the rotor. Adjust for change in radius from rotor inlet to exit
299
- row.T0R = (upstream_rothalpy + 0.5*row.U**2)/row.Cp - T0_coolant_weighted_average(row)
300
+ row.T0R = upstream.T0R # (upstream_rothalpy + 0.5*row.U**2)/row.Cp # - T0_coolant_weighted_average(row)
300
301
  P0R_P = row.P0R / row.P
301
302
  T0R_T = P0R_P**((row.gamma-1)/row.gamma)
302
303
  row.T = (row.T0R/T0R_T) # Exit static temperature
@@ -326,7 +327,7 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
326
327
  row.Vt = row.Wt+row.U
327
328
 
328
329
  row.alpha2 = np.arctan2(row.Vt,row.Vm)
329
- row.V = np.sqrt(row.Vx**2 + row.Vr**2 + row.Vt**2)
330
+ row.V = np.sqrt(row.Vm**2*(1+np.tan(row.alpha2)**2))
330
331
 
331
332
  row.M = row.V/np.sqrt(row.gamma*row.R*row.T)
332
333
  T0_T = (1+(row.gamma-1)/2 * row.M**2)
@@ -369,7 +370,7 @@ def inlet_calc(row:BladeRow):
369
370
  raise ValueError(f"Unusually slow flow:{iter} Mach:{avg_mach}")
370
371
  row.Vm[0] = 1/(len(row.Vm)-1)*row.Vm[1:].sum() # Initialize the value at the hub to not upset the mean
371
372
  row.Vr = row.Vm*np.sin(row.phi)
372
- row.Vt = row.Vm*np.cos(row.phi)*np.tan(row.alpha2)
373
+ row.Vt = row.Vm*np.tan(row.alpha2)
373
374
  row.V = np.sqrt(row.Vt**2+row.Vm**2)
374
375
  # Fine tune the Temperature and Pressure and density
375
376
  row.M = row.V/np.sqrt(row.gamma*row.R*row.T)
@@ -13,6 +13,7 @@ from .solve_radeq import adjust_streamlines, radeq
13
13
  from scipy.optimize import minimize_scalar, differential_evolution, fmin_slsqp
14
14
  from .inlet import Inlet
15
15
  from .outlet import Outlet
16
+ from pyturbo.helper import convert_to_ndarray
16
17
 
17
18
  class TurbineSpool(Spool):
18
19
 
@@ -193,7 +194,7 @@ class TurbineSpool(Spool):
193
194
  """
194
195
  total_massflow = list(); s = 0; massflow_stage = list()
195
196
  stage_ids = list(set([row.stage_id for row in blade_rows if row.stage_id>=0]))
196
- for row in blade_rows[1:-1]: # Ignore inlet and outlet
197
+ for row in blade_rows: # Ignore inlet and outlet
197
198
  total_massflow.append(row.total_massflow_no_coolant)
198
199
  sign = 1
199
200
  for s in stage_ids:
@@ -237,7 +238,7 @@ class TurbineSpool(Spool):
237
238
  2. Keep the mean
238
239
 
239
240
  Args:
240
- x0 (List[float]): _description_
241
+ x0 (List[float]): Percentage of P0 exiting each row
241
242
  blade_rows (List[List[BladeRow]]): _description_
242
243
  P0 (npt.NDArray): _description_
243
244
  P (npt.NDArray): (1) Outlet Static Pressure. (2)
@@ -250,9 +251,9 @@ class TurbineSpool(Spool):
250
251
  # try:
251
252
  if balance_mean_pressure:
252
253
  for j in range(self.num_streamlines):
253
- Ps_range = outlet_pressure(x0,P0[j],P[j])
254
+ Ps = outlet_pressure(x0,P0[j],P[j])
254
255
  for i in range(1,len(blade_rows)-2):
255
- blade_rows[i].P[j] = Ps_range[i-1]
256
+ blade_rows[i].P[j] = float(Ps[i-1])
256
257
  blade_rows[-2].P = P
257
258
  else:
258
259
  for i in range(1,len(blade_rows)-1):
@@ -261,7 +262,7 @@ class TurbineSpool(Spool):
261
262
  # try:
262
263
  calculate_massflows(blade_rows,True,self.fluid)
263
264
  print(x0)
264
- return self.__massflow_std__(blade_rows)
265
+ return self.__massflow_std__(blade_rows[1:-1]) # do not consider inlet and outlet
265
266
  # except Exception as e:
266
267
  # print(e)
267
268
  # finally:
@@ -280,7 +281,7 @@ class TurbineSpool(Spool):
280
281
  past_err = -100; loop_iter = 0; err = 0.001
281
282
  while (np.abs((err-past_err)/err)>0.05) and loop_iter<10:
282
283
  if len(outlet_P) == 1:
283
- # x = balance_massflows(0.658,self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P)
284
+ # x = balance_massflows(0.22896832148169688,self.blade_rows,self.blade_rows[0].P0,self.blade_rows[-1].P)
284
285
  res = minimize_scalar(fun=balance_massflows,args=(self.blade_rows,self.blade_rows[0].P0,self.blade_rows[-1].P),bounds=outlet_P[0],tol=0.001,options={'disp': True},method='bounded')
285
286
  x = res.x
286
287
  print(x)
@@ -395,64 +396,63 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False,fluid:
395
396
  passage (Passage): _description_
396
397
  calculate_vm (bool, optional): _description_. Defaults to False.
397
398
  """
398
- for p in range(3):
399
- for i in range(1,len(blade_rows)-1):
400
- row = blade_rows[i]
401
- # Upstream Row
402
- if i == 0:
403
- upstream = blade_rows[i]
404
- else:
405
- upstream = blade_rows[i-1]
406
- if i<len(blade_rows)-1:
407
- downstream = blade_rows[i+1]
408
-
409
- # Pressure loss = shift in entropy which affects the total pressure of the row
410
- if row.row_type == RowType.Inlet:
411
- row.Yp = 0
412
- else:
413
- if row.loss_function.loss_type == LossType.Pressure:
414
- row.Yp = row.loss_function(row,upstream)
415
- for _ in range(2):
416
- if row.row_type == RowType.Rotor:
417
- rotor_calc(row,upstream,calculate_vm=True)
418
- # Finds Equilibrium between Vm, P0, T0
419
- row = radeq(row,upstream,downstream)
420
- compute_gas_constants(row,fluid)
421
- rotor_calc(row,upstream,calculate_vm=False)
422
- elif row.row_type == RowType.Stator:
423
- stator_calc(row,upstream,downstream,calculate_vm=True)
424
- # Finds Equilibrium between Vm, P0, T0
425
- row = radeq(row,upstream,downstream)
426
- compute_gas_constants(row,fluid)
427
- stator_calc(row,upstream,downstream,calculate_vm=False)
428
- compute_gas_constants(row,fluid)
429
- compute_massflow(row)
430
- compute_power(row,upstream)
431
-
432
- elif row.loss_function.loss_type == LossType.Enthalpy:
399
+ for i in range(1,len(blade_rows)-1):
400
+ row = blade_rows[i]
401
+ # Upstream Row
402
+ if i == 0:
403
+ upstream = blade_rows[i]
404
+ else:
405
+ upstream = blade_rows[i-1]
406
+ if i<len(blade_rows)-1:
407
+ downstream = blade_rows[i+1]
408
+
409
+ # Pressure loss = shift in entropy which affects the total pressure of the row
410
+ if row.row_type == RowType.Inlet:
411
+ row.Yp = 0
412
+ else:
413
+ if row.loss_function.loss_type == LossType.Pressure:
414
+ row.Yp = row.loss_function(row,upstream)
415
+ for _ in range(2):
433
416
  if row.row_type == RowType.Rotor:
434
- row.Yp = 0
435
- rotor_calc(row,upstream,calculate_vm=calculate_vm)
436
- eta_total = float(row.loss_function(row,upstream))
437
- def find_yp(Yp,row,upstream):
438
- row.Yp = Yp
439
- rotor_calc(row,upstream,calculate_vm=True)
440
- row = radeq(row,upstream)
441
- compute_gas_constants(row,fluid)
442
- rotor_calc(row,upstream,calculate_vm=False)
443
- return abs(row.eta_total - eta_total)
444
-
445
- res = minimize_scalar(find_yp,bounds=[0,0.6],args=(row,upstream))
446
- row.Yp = res.x
417
+ rotor_calc(row,upstream,calculate_vm=True)
418
+ # Finds Equilibrium between Vm, P0, T0
419
+ row = radeq(row,upstream,downstream)
420
+ compute_gas_constants(row,fluid)
421
+ rotor_calc(row,upstream,calculate_vm=False)
447
422
  elif row.row_type == RowType.Stator:
448
- row.Yp = 0
449
423
  stator_calc(row,upstream,downstream,calculate_vm=True)
450
- row = radeq(row,upstream)
451
- row = compute_gas_constants(row,fluid)
424
+ # Finds Equilibrium between Vm, P0, T0
425
+ row = radeq(row,upstream,downstream)
426
+ compute_gas_constants(row,fluid)
452
427
  stator_calc(row,upstream,downstream,calculate_vm=False)
453
- row = compute_gas_constants(row,fluid)
428
+ compute_gas_constants(row,fluid)
454
429
  compute_massflow(row)
455
430
  compute_power(row,upstream)
431
+
432
+ elif row.loss_function.loss_type == LossType.Enthalpy:
433
+ if row.row_type == RowType.Rotor:
434
+ row.Yp = 0
435
+ rotor_calc(row,upstream,calculate_vm=calculate_vm)
436
+ eta_total = float(row.loss_function(row,upstream))
437
+ def find_yp(Yp,row,upstream):
438
+ row.Yp = Yp
439
+ rotor_calc(row,upstream,calculate_vm=True)
440
+ row = radeq(row,upstream)
441
+ compute_gas_constants(row,fluid)
442
+ rotor_calc(row,upstream,calculate_vm=False)
443
+ return abs(row.eta_total - eta_total)
444
+
445
+ res = minimize_scalar(find_yp,bounds=[0,0.6],args=(row,upstream))
446
+ row.Yp = res.x
447
+ elif row.row_type == RowType.Stator:
448
+ row.Yp = 0
449
+ stator_calc(row,upstream,downstream,calculate_vm=True)
450
+ row = radeq(row,upstream)
451
+ row = compute_gas_constants(row,fluid)
452
+ stator_calc(row,upstream,downstream,calculate_vm=False)
453
+ row = compute_gas_constants(row,fluid)
454
+ compute_massflow(row)
455
+ compute_power(row,upstream)
456
456
 
457
457
  def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:BladeRow,downstream:BladeRow=None,fluid:Solution=None):
458
458
  """Finds the blade exit angles that balance the massflow throughout the stage
@@ -533,14 +533,10 @@ def outlet_pressure(percents:List[float],inletP0:float,outletP:float) -> npt.NDA
533
533
  Returns:
534
534
  npt.NDArray: Array of static pressures
535
535
  """
536
- maxP = inletP0
537
- minP = outletP
538
- if isinstance(percents, float):
539
- Ps = [percents*(maxP-minP)+minP]
540
- else:
541
- Ps = np.zeros(shape=(len(percents),1)); i = 0
542
- for p in percents:
543
- Ps[i] = p*(maxP - minP) + minP
544
- maxP = Ps[i]
545
- i+=1
546
- return Ps
536
+ percents = convert_to_ndarray(percents)
537
+ Ps = np.zeros((len(percents),))
538
+ for i in range(len(percents)):
539
+ Ps[i] = float(interp1d((0,1),(inletP0,outletP))(percents[i]))
540
+ inletP0 = Ps[i]
541
+ return Ps
542
+
@@ -1,286 +0,0 @@
1
- from scipy.interpolate import interp1d,PchipInterpolator
2
- from scipy.integrate import odeint
3
- import numpy as np
4
- import numdifftools as nd
5
- from .bladerow import BladeRow
6
- from .enums import RowType
7
-
8
-
9
- def radeq(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None) -> BladeRow:
10
- """Solves the radial equilibrium equation for axial machines and returns the convergence.
11
-
12
- Note:
13
- This function will give you T0, P0, Vm as a function of the radius.
14
-
15
- Args:
16
- row (BladeRow): Current row
17
- upstream (BladeRow): Previous row
18
- downstream (BladeRow): Next row
19
-
20
- Returns:
21
- BladeRow: current row with T0, P0, and Vm calculated
22
- """
23
- row_radius = row.r # Use these for gradient
24
- up_radius = upstream.r
25
-
26
- def ode_radeq_streamtube(r:np.ndarray,y:np.ndarray):
27
- """Solves the radial equilibrium equation for a streamtube
28
-
29
- Args:
30
- r (np.ndarray): radius not as a percent
31
- y (np.ndarray): Array containing [P0,Vt,VtU,T0]
32
-
33
- """
34
- P0 = y[0]
35
- T0 = y[1]
36
- Vm = y[2]
37
- if r>row_radius[-1]:
38
- return [0,0,0]
39
- elif r<row_radius[0]:
40
- return [0,0,0]
41
-
42
- Cp = row.Cp
43
- phi = interp1d(row_radius, row.phi)(r)
44
- alpha = interp1d(row_radius, row.alpha2)(r)
45
- rm = interp1d(row_radius, row.rm)(r)
46
- rho = row.rho.mean()
47
-
48
- if (row.row_type == RowType.Rotor):
49
- omega = row.rpm*np.pi/30
50
- U = omega*r
51
- else:
52
- omega = 0
53
- U = 0
54
- gamma = row.gamma
55
-
56
- # Solve the Radial Equlibrium
57
- Vt = Vm*np.tan(alpha)
58
- Vr = Vm*np.sin(phi)
59
- # Estimations
60
- dVm_dr = float(interp1d(row_radius, np.gradient(row.Vm, row_radius))(r))
61
- dVt_dr = dVm_dr*np.tan(alpha)
62
- # dVr_dr = dVm_dr*np.sin(phi)
63
-
64
- up_Vm = interp1d(row_radius, upstream.Vm)(r)
65
- if downstream:
66
- if downstream.row_type == RowType.Outlet:
67
- down_Vm = Vm
68
- else:
69
- down_Vm = interp1d(row_radius, downstream.Vm)(r)
70
- else:
71
- down_Vm = Vm
72
- up_m = interp1d(row_radius, upstream.m)(r)
73
-
74
- # Get a rough guess of dVm/dm
75
- if downstream!=None:
76
- down_m = interp1d(row_radius, downstream.m)(r)
77
- row_m = interp1d(row_radius, row.m)(r)
78
- if down_m != row_m:
79
- func_Vm_m = PchipInterpolator([up_m, row_m, down_m],[up_Vm, Vm, down_Vm])
80
- else:
81
- func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
82
- else:
83
- func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
84
- dVm_dm = func_Vm_m.derivative()(row_m)
85
-
86
- # Upstream
87
- dT0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.T0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
88
- dP0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.P0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
89
-
90
- dP0_dr = float(interp1d(row.percent_hub_shroud, np.gradient(row.P0,row_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get
91
-
92
- U_up = float(interp1d(upstream.percent_hub_shroud,up_radius*omega)((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
93
- dVtup_dr = float(interp1d(upstream.percent_hub_shroud,np.gradient(upstream.Vt,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0])))
94
- Vtup = float(interp1d(upstream.percent_hub_shroud,upstream.Vt)((r-row_radius[0])/(row_radius[-1]-row_radius[0])))
95
-
96
-
97
- dT0_dr = dT0up_dr - 1/row.Cp*(U_up*dVtup_dr + Vtup*omega - (U*dVt_dr+Vt*omega)) # Eqn 8
98
- # if row.loss_function.LossType == LossType.Pressure: # type: ignore
99
- # dP0_dr = dP0up_dr-row.Yp*(dP0up_dr - dP_dr) # Eqn 9
100
-
101
- C = Vm**2*(1+np.tan(alpha)**2)/(2*Cp*T0)
102
- if (C>1) & ((gamma/(gamma-1))<2):
103
- raise Exception("Invalid value of C {C}, change reduce alpha or Vm")
104
- B = (1-C)**(gamma/(gamma-1))
105
- A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1)) * (1+np.tan(alpha)**2)/(2*Cp)
106
-
107
- epsilon = 1e-10 # or another small threshold
108
- if abs(rm) > epsilon:
109
- dVm_dr = Vm/(2*T0**2) * dT0_dr - rho/(2*A*Vm) * (Vt**2/r + Vm/rm*np.cos(phi)-Vr*dVm_dm) + B/(2*A*Vm) * dP0_dr # Eqn 21
110
- else:
111
- dVm_dr = Vm/(2*T0**2) * dT0_dr - rho/(2*A*Vm) * (Vt**2/r) + B/(2*A*Vm) * dP0_dr # Eqn 21, modified
112
-
113
- ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
114
-
115
- return ydot
116
-
117
- T0 = row.T0
118
-
119
- P0 = row.P0
120
- Vm = row.Vm
121
-
122
- # Estimate the Vt based on a given turning angle
123
- mean_radius = row_radius.mean()
124
- tip_radius = row_radius[-1]
125
- hub_radius = row_radius[0]
126
-
127
- T0m = interp1d(row.percent_hub_shroud,T0)(0.5);
128
- P0m = interp1d(row.percent_hub_shroud,P0)(0.5); Vmm = interp1d(row.percent_hub_shroud,Vm)(0.5)
129
- # We are solving for the values of these quantities at row exit
130
- ics = np.array([P0m,T0m,Vmm])
131
-
132
- rm_to_tip = np.linspace(mean_radius,tip_radius)
133
- res1 = odeint(ode_radeq_streamtube, ics, rm_to_tip, tfirst=True)
134
-
135
- rm_to_hub = np.flip(np.linspace(hub_radius,mean_radius))
136
- res2 = odeint(ode_radeq_streamtube, ics, rm_to_hub, tfirst=True)
137
-
138
- res2 = np.flipud(res2)
139
- res = np.concatenate([res2[:-1,:],res1])
140
- r = np.concatenate([np.flip(rm_to_hub)[:-1], rm_to_tip])
141
-
142
- P0_new = interp1d(r,res[:,0])(row_radius)
143
- T0_new = interp1d(r,res[:,1])(row_radius)
144
- Vm_new = interp1d(r,res[:,2])(row_radius)
145
-
146
- row.P0 = P0_new
147
- row.T0 = T0_new
148
- row.Vm = Vm_new
149
- if row.row_type == RowType.Rotor:
150
- # U(VT1-VT2) = Power/massflow; VT2 = VT1 - Power/massflow
151
- row.Vt = upstream.Vt-row.power/(row.total_massflow*row.U)
152
- row.alpha2 = np.arctan2(row.Vt,row.Vx)
153
- elif row.row_type == RowType.Stator:
154
- row.Vt = row.Vm*np.cos(row.phi)*np.tan(row.alpha2)
155
- row.Vr = row.Vm*np.sin(row.phi)
156
- row.Vx = row.Vm*np.cos(row.phi)
157
-
158
- return row
159
-
160
-
161
- def radeq_normalized(row:BladeRow,upstream:BladeRow) -> BladeRow:
162
- """Solves the radial equilibrium equation for axial or radial machines and returns the convergence.
163
-
164
- Args:
165
- row (BladeRow): Current row
166
- upstream (BladeRow): Previous row
167
-
168
- Returns:
169
- BladeRow: current row with Vt, T0, P0, and Vm calculated
170
- """
171
- _,row_radius = row.streamline.get_point(row.percent_hub_shroud) # Use these for gradient
172
- _,up_radius = upstream.streamline.get_point(upstream.percent_hub_shroud)
173
-
174
- def ode_radeq_streamtube(t:np.ndarray,y:np.ndarray):
175
- """Solves the radial equilibrium equation for a streamtube
176
-
177
- Args:
178
- t (np.ndarray): percent from hub to shroud
179
- y (np.ndarray): Array containing [P0,Vt,VtU,T0]
180
-
181
- """
182
- P0 = y[0]
183
- T0 = y[1]
184
- Vm = y[2]
185
- if t>1:
186
- return [0,0,0]
187
- elif t<0:
188
- return [0,0,0]
189
-
190
- _,r = row.streamline.get_point()
191
- Cp = row.Cp
192
- # Interpolate angle of inclination (phi), exit flow angle (alpha), radius of curvature (rm) at a particular percentage from hub to shroud
193
- phi = interp1d(row.percent_hub_shroud, row.phi)(t)
194
- alpha = interp1d(row.percent_hub_shroud, row.alpha2)(t)
195
- rm = interp1d(row.percent_hub_shroud,row.rm)(t)
196
- rho = interp1d(row.percent_hub_shroud,row.rho)(t)
197
-
198
-
199
- if (row.row_type == RowType.Rotor):
200
- omega = row.rpm*np.pi/30
201
- U = omega*r
202
- else:
203
- omega = 0
204
- U = 0
205
- gamma = row.gamma
206
-
207
- # Solve the Radial Equlibrium
208
- Vt = Vm*np.cos(phi)*np.tan(alpha)
209
- Vr = float(interp1d(row.percent_hub_shroud, row.Vr)(t))
210
- # Estimations: need the radius of the streamline to compute gradients
211
- dVm_dr = interp1d(row_radius,np.gradient(row.Vm, row_radius))(r)
212
- dVt_dr = dVm_dr*np.cos(phi)*np.tan(alpha)
213
-
214
- dVm_dm = interp1d(row_radius, np.gradient(Vm, r)) + interp1d(x, np.gradient(Vm, r))
215
-
216
- # Upstream: We interpolate the gradient based on the percentage from hub to shroud
217
- dT0up_dr = interp1d(upstream.percent_hub_shroud,
218
- np.gradient(upstream.T0,up_radius))(t)
219
- dP0up_dr = interp1d(upstream.percent_hub_shroud,
220
- np.gradient(upstream.P0,up_radius))(t)
221
- dP0_dr = interp1d(upstream.percent_hub_shroud,
222
- np.gradient(upstream.P0,up_radius))(t)
223
-
224
- U_up = interp1d(upstream.percent_hub_shroud,up_radius*omega)(t) # use percentage to get the T0 upstream value
225
- dVtup_dr = interp1d(upstream.percent_hub_shroud,np.gradient(upstream.Vt,up_radius))(t)
226
- Vtup = interp1d(upstream.percent_hub_shroud,upstream.Vt)(t)
227
-
228
- dP_dr = interp1d(row.percent_hub_shroud,np.gradient(row.P,row_radius))(t)
229
- dT0_dr = dT0up_dr - 1/row.Cp*(U_up*dVtup_dr + Vtup*omega - (U*dVt_dr+Vt*omega)) # Eqn 8
230
- # if row.loss_function.LossType == LossType.Pressure: # type: ignore
231
- # dP0_dr = dP0up_dr-row.Yp*(dP0up_dr - dP_dr) # Eqn 9
232
-
233
- C = Vm**2*(1+np.cos(phi)**2 * np.tan(alpha)**2)/(2*Cp*T0)
234
- B = (1-C)**(gamma/(gamma-1))
235
- A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1))
236
- dVm_dr = 1/(2*Vm*A) * (rho*(Vt/r - Vm**2/rm * np.cos(phi)-Vr*dVm_dm) - dP0_dr*B) + 1/(2*T0) *dT0_dr # Eqn 6
237
-
238
- ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
239
-
240
- return ydot
241
-
242
- T0 = row.T0
243
- P0 = row.P0
244
- Vm = row.Vm
245
-
246
- # Estimate the Vt based on a given turning angle
247
- _, mean_radius = row.streamline.get_point(0.5)
248
- _, tip_radius = row.streamline.get_point(1)
249
- _, hub_radius = row.streamline.get_point(0)
250
-
251
- T0m = interp1d(row.percent_hub_shroud,T0)(0.5);
252
- P0m = interp1d(row.percent_hub_shroud,P0)(0.5);
253
- Vmm = interp1d(row.percent_hub_shroud,Vm)(0.5)
254
- # We are solving for the values of these quantities at row exit
255
- ics = np.array([P0m,T0m,Vmm])
256
-
257
- mid_to_tip = np.linspace(0,1)
258
- res1 = odeint(ode_radeq_streamtube, ics, mid_to_tip, tfirst=True) # Results
259
-
260
- mid_to_hub = np.flip(np.linspace(hub_radius,mean_radius))
261
- res2 = odeint(ode_radeq_streamtube, ics, mid_to_hub, tfirst=True) # Results
262
-
263
- res2 = np.flipud(res2)
264
- res = np.concatenate([res2[:-1,:],res1]) # Combine the results
265
- t = np.concatenate([np.flip(mid_to_hub)[:-1], mid_to_tip])
266
-
267
- P0_new = interp1d(t,res[:,0])(row.percent_hub_shroud)
268
- T0_new = interp1d(t,res[:,1])(row.percent_hub_shroud)
269
- Vm_new = interp1d(t,res[:,2])(row.percent_hub_shroud)
270
-
271
- row.P0 = P0_new
272
- row.T0 = T0_new
273
- row.Vm = Vm_new
274
- if row.row_type == RowType.Rotor:
275
- # U(VT1-VT2) = Power/massflow; VT2 = VT1 - Power/massflow
276
- row.Vt = upstream.Vt-row.power/(row.total_massflow*row.U)
277
- row.alpha2 = np.arctan2(row.Vt,row.Vx)
278
- elif row.row_type == RowType.Stator:
279
- row.Vt = row.Vm*np.cos(row.phi)*np.tan(row.alpha2)
280
- row.Vr = row.Vm*np.sin(row.phi)
281
- row.Vx = row.Vm*np.cos(row.phi)
282
-
283
- return row
284
-
285
-
286
-