turbo-design 1.3.5__tar.gz → 1.3.7__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 (35) hide show
  1. {turbo_design-1.3.5 → turbo_design-1.3.7}/PKG-INFO +4 -2
  2. {turbo_design-1.3.5 → turbo_design-1.3.7}/pyproject.toml +1 -1
  3. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/bladerow.py +19 -13
  4. turbo_design-1.3.7/turbodesign/loss/__init__.py +1 -0
  5. turbo_design-1.3.7/turbodesign/loss/losstype.py +55 -0
  6. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/ainleymathieson.py +6 -6
  7. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/craigcox.py +21 -21
  8. turbo_design-1.3.7/turbodesign/loss/turbine/kackerokapuu.py +166 -0
  9. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/traupel.py +15 -15
  10. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/passage.py +9 -4
  11. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/td_math.py +32 -3
  12. turbo_design-1.3.5/turbodesign/loss/__init__.py +0 -1
  13. turbo_design-1.3.5/turbodesign/loss/losstype.py +0 -25
  14. turbo_design-1.3.5/turbodesign/loss/turbine/kackerokapuu.py +0 -129
  15. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/__init__.py +0 -0
  16. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/arrayfuncs.py +0 -0
  17. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/cantera_gas/co2.yaml +0 -0
  18. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/compressorspool.py +0 -0
  19. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/coolant.py +0 -0
  20. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/enums.py +0 -0
  21. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/inlet.py +0 -0
  22. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/isentropic.py +0 -0
  23. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/compressor/__init__.py +0 -0
  24. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/TD2.py +0 -0
  25. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/__init__.py +0 -0
  26. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/fixedefficiency.py +0 -0
  27. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/loss/turbine/fixedpressureloss.py +0 -0
  28. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/lossinterp.py +0 -0
  29. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/outlet.py +0 -0
  30. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/radeq.py +0 -0
  31. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/rotor.py +0 -0
  32. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/solve_radeq.py +0 -0
  33. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/spool.py +0 -0
  34. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/stage.py +0 -0
  35. {turbo_design-1.3.5 → turbo_design-1.3.7}/turbodesign/turbinespool.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: turbo-design
3
- Version: 1.3.5
3
+ Version: 1.3.7
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,8 @@ 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
14
+ Classifier: Programming Language :: Python :: 3.14
13
15
  Requires-Dist: cantera
14
16
  Requires-Dist: findiff
15
17
  Requires-Dist: matplotlib
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "turbo-design"
3
- version = "1.3.5"
3
+ version = "1.3.7"
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 = [
@@ -1,5 +1,5 @@
1
1
  from dataclasses import field, Field
2
- from typing import Any, Callable, List, Optional, Tuple, Union
2
+ from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
3
3
  from .enums import RowType, PowerType
4
4
  import numpy as np
5
5
  import numpy.typing as npt
@@ -9,7 +9,7 @@ from cantera import Solution, composite
9
9
  from .coolant import Coolant
10
10
  from pyturbo.helper import line2D
11
11
  from pyturbo.aero.airfoil2D import Airfoil2D
12
- from .loss import LossBaseClass
12
+ from .loss import LossBaseClass, CompositeLossModel
13
13
  from .passage import Passage
14
14
 
15
15
 
@@ -406,19 +406,25 @@ class BladeRow:
406
406
  return self.loss_function
407
407
 
408
408
  @loss_model.setter
409
- def loss_model(self, model:Callable[[Any], Any]):
410
- """Add in custom loss model
409
+ def loss_model(self, model:Union[LossBaseClass, Sequence[LossBaseClass]]):
410
+ """Assign one or more loss models that inherit :class:`LossBaseClass`.
411
411
 
412
412
  Args:
413
- model (function): custom loss function. Input will be of the format blade row
414
-
415
- Example:
416
-
417
- def mylossfunction(row:BladeRow) -> float
418
- code to do something with machine learning
419
- return pressure loss
413
+ model: Either a single loss model or a sequence of models.
420
414
  """
421
- self.loss_function = model # type: ignore
415
+ if isinstance(model, LossBaseClass):
416
+ self.loss_function = model
417
+ return
418
+
419
+ if isinstance(model, Sequence):
420
+ if len(model) == 0:
421
+ raise ValueError("At least one loss model must be provided.")
422
+ if not all(isinstance(m, LossBaseClass) for m in model):
423
+ raise TypeError("All entries must inherit LossBaseClass.")
424
+ self.loss_function = CompositeLossModel(model)
425
+ return
426
+
427
+ raise TypeError("Loss models must inherit LossBaseClass.")
422
428
 
423
429
  @property
424
430
  def te_pitch(self):
@@ -631,4 +637,4 @@ def compute_gas_constants(row:BladeRow,fluid:Optional[Solution]=None) -> None:
631
637
  row.gamma = row.Cp/row.Cv
632
638
  # Use Ideal Gas
633
639
  row.rho = row.P/(row.T*row.R)
634
- row.mu = sutherland(row.T) # type: ignore
640
+ row.mu = sutherland(row.T) # type: ignore
@@ -0,0 +1 @@
1
+ from .losstype import LossType, LossBaseClass, CompositeLossModel
@@ -0,0 +1,55 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, Iterable, Tuple
3
+ from ..lossinterp import LossInterp
4
+ import os
5
+ from ..enums import LossType
6
+
7
+ class LossBaseClass(ABC):
8
+ data: Dict[str,LossInterp]
9
+ _loss_type:LossType
10
+
11
+ def __init__(self,lossType:LossType):
12
+ # Make the environment directory
13
+ default_home = os.path.join(os.path.expanduser("~"), ".cache")
14
+ os.environ['TD3_HOME'] = os.path.join(default_home,'TD3_LossModels')
15
+ os.makedirs(os.environ['TD3_HOME'],exist_ok=True)
16
+
17
+ self._loss_type = lossType
18
+
19
+ @abstractmethod
20
+ def __call__(self, row:Any, upstream:Any) -> float:
21
+ """Evaluate the loss for the supplied blade row."""
22
+ raise NotImplementedError
23
+
24
+
25
+
26
+ @property
27
+ def loss_type(self):
28
+ return self._loss_type
29
+
30
+
31
+ class CompositeLossModel(LossBaseClass):
32
+ """Combines multiple loss models of the same type."""
33
+
34
+ def __init__(self, models: Iterable[LossBaseClass]):
35
+ models_tuple: Tuple[LossBaseClass, ...] = tuple(models)
36
+ if not models_tuple:
37
+ raise ValueError("CompositeLossModel requires at least one loss model.")
38
+
39
+ loss_type = models_tuple[0].loss_type
40
+ for model in models_tuple[1:]:
41
+ if model.loss_type != loss_type:
42
+ raise ValueError("All loss models must share the same LossType.")
43
+
44
+ super().__init__(loss_type)
45
+ self._models: Tuple[LossBaseClass, ...] = models_tuple
46
+
47
+ def __call__(self, row: Any, upstream: Any) -> float:
48
+ total_loss = 0.0
49
+ for model in self._models:
50
+ total_loss += float(model(row, upstream))
51
+ return total_loss
52
+
53
+ @property
54
+ def models(self) -> Tuple[LossBaseClass, ...]:
55
+ return self._models
@@ -69,7 +69,7 @@ class AinleyMathieson(LossBaseClass):
69
69
  beta1 = -np.abs(np.radians(row.beta1_metal))
70
70
  alpha2 = np.abs(row.alpha2)
71
71
  if row.M<0.5:
72
- alpha2 = self.data['Fig05'](np.degrees(np.cos(row.throat/row.pitch)))
72
+ alpha2 = self.data['Fig05'](float(np.degrees(np.cos(row.throat/row.pitch))))
73
73
  elif row.M<0.95:
74
74
  X = 0.7
75
75
  alpha2 = np.arctan(
@@ -88,7 +88,7 @@ class AinleyMathieson(LossBaseClass):
88
88
  beta1 = np.abs(np.radians(row.beta1_metal))
89
89
  alpha2 = -np.abs(row.beta2)
90
90
  if row.M_rel<0.5:
91
- alpha2 = self.data['Fig05'](np.degrees(np.cos(row.throat/row.pitch)))
91
+ alpha2 = self.data['Fig05'](float(np.degrees(np.cos(row.throat/row.pitch))))
92
92
  elif row.M_rel<0.95:
93
93
  X = 1.35 # Shrouded Blade
94
94
  alpha2 = np.arctan(
@@ -106,18 +106,18 @@ class AinleyMathieson(LossBaseClass):
106
106
  ID = (upstream.r[-1]+row.r[-1])/2; OD = (upstream.r[-1]+row.r[-1])/2
107
107
  A1 = np.pi*(upstream.r[-1]**2 - upstream.r[0]**2)*np.cos(beta1)
108
108
  A2 = np.pi*(row.r[-1]**2 - row.r[0]**2)*np.cos(alpha2)
109
- lam = self.data['Fig08']((A2/A1)**2/(1+ID/OD)) # Eqn but using Figure 8
109
+ lam = self.data['Fig08'](float((A2/A1)**2/(1+ID/OD))) # Eqn but using Figure 8
110
110
  alpha_m = np.arctan((np.tan(alpha1) - np.tan(alpha2))/2)
111
111
  Cl_s_c = 2*(np.tan(alpha1)-np.tan(alpha2))*np.cos(alpha_m)
112
112
  # calculated but not used
113
113
  Y_secondary_clearance = (lam + B * k/h) * (Cl_s_c)**2 * (np.cos(alpha2)/np.cos(alpha_m)) # Eqn 6
114
114
 
115
115
 
116
- Yp_beta0 = self.data['Fig04a'](s_c, np.degrees(alpha2))
117
- Yp_beta1_eq_alpha2 = self.data['Fig04b'](s_c, np.degrees(alpha2))
116
+ Yp_beta0 = self.data['Fig04a'](float(s_c), float(np.degrees(alpha2)))
117
+ Yp_beta1_eq_alpha2 = self.data['Fig04b'](float(s_c), float(np.degrees(alpha2)))
118
118
 
119
119
  Yp_i0 = (Yp_beta0 + (beta1/alpha2)**2 *(Yp_beta1_eq_alpha2 - Yp_beta0)) *(t_c/0.2)**(-beta1/alpha2) # Fig 4 and Eqn 5
120
120
 
121
121
  Yt = Yp_i0 + Y_secondary_clearance
122
122
  return Yt # Profile loss at zero incidence
123
-
123
+
@@ -116,58 +116,58 @@ class CraigCox(LossBaseClass):
116
116
 
117
117
  asin_os = np.degrees(np.arcsin(currentRow.throat/currentRow.pitch))
118
118
 
119
- N_pr = self.data['Fig03'](Re,0.05) # use a good finish for the geometry
119
+ N_pr = self.data['Fig03'](float(Re), 0.05) # use a good finish for the geometry
120
120
  if (inlet_flow_angle-imin < 10):
121
121
  Fl = 13
122
122
  else:
123
- Fl = self.data['Fig04'](outlet_flow_angle,inlet_flow_angle-imin)
123
+ Fl = self.data['Fig04'](float(outlet_flow_angle), float(inlet_flow_angle-imin))
124
124
 
125
125
  x = 1-np.sin(np.radians(outlet_flow_angle))/np.sin(np.radians(inlet_flow_angle))
126
- contraction_ratio = self.data['Fig07'](x,s_b) # contraction ratio
126
+ contraction_ratio = self.data['Fig07'](float(x), float(s_b)) # contraction ratio
127
127
 
128
- X_pb = self.data['Fig05'](Fl*s_b,contraction_ratio)
129
- delta_X_pt = self.data['Fig06_delta_Xpt'](currentRow.te_pitch)
130
- N_pt = self.data['Fig06_Npt'](currentRow.te_pitch,outlet_flow_angle)
131
- delta_Xpm = self.data['Fig08'](M_out,np.degrees(np.arcsin((currentRow.throat+te)/currentRow.pitch)))
132
- delta_Xp_se = self.data['Fig09'](e_s,M_out)
128
+ X_pb = self.data['Fig05'](float(Fl*s_b), float(contraction_ratio))
129
+ delta_X_pt = self.data['Fig06_delta_Xpt'](float(currentRow.te_pitch))
130
+ N_pt = self.data['Fig06_Npt'](float(currentRow.te_pitch), float(outlet_flow_angle))
131
+ delta_Xpm = self.data['Fig08'](float(M_out), float(np.degrees(np.arcsin((currentRow.throat+te)/currentRow.pitch))))
132
+ delta_Xp_se = self.data['Fig09'](float(e_s), float(M_out))
133
133
 
134
- Fi = self.data['Fig15'](blade_inlet_angle,s_b)
134
+ Fi = self.data['Fig15'](float(blade_inlet_angle), float(s_b))
135
135
  # Incidence Effects
136
136
  if currentRow.beta1_fixed:
137
137
  if incidence_angle>0: # Positive incidence
138
- stall_incidence_angle = self.data['Fig11'](currentRow.beta1.mean(),asin_os)
138
+ stall_incidence_angle = self.data['Fig11'](float(currentRow.beta1.mean()), float(asin_os))
139
139
 
140
140
  incidence_ratio = (incidence_angle - imin)/(stall_incidence_angle-imin)
141
141
 
142
- i_plus_istall_sb = self.data['Fig12_sb'](s_b,asin_os)
143
- i_plus_istall_cor = self.data['Fig12_cr'](contraction_ratio,asin_os)
142
+ i_plus_istall_sb = self.data['Fig12_sb'](float(s_b), float(asin_os))
143
+ i_plus_istall_cor = self.data['Fig12_cr'](float(contraction_ratio), float(asin_os))
144
144
 
145
145
  if blade_inlet_angle<=90:
146
- i_plus_istall_basic = self.data['Fig11'](inlet_flow_angle,asin_os)
146
+ i_plus_istall_basic = self.data['Fig11'](float(inlet_flow_angle), float(asin_os))
147
147
  i_plus_istall = i_plus_istall_basic + i_plus_istall_sb + i_plus_istall_cor # Eqn 5
148
148
  else:
149
- i_plus_istall_basic = self.data["Fig14_i+istall"](blade_inlet_angle,asin_os)
149
+ i_plus_istall_basic = self.data["Fig14_i+istall"](float(blade_inlet_angle), float(asin_os))
150
150
  i_plus_istall = i_plus_istall_basic + (1-(blade_inlet_angle-90)/(90-asin_os))*(i_plus_istall_sb + i_plus_istall_cor) # Eqn 7
151
151
  else:
152
- i_minus_istall_sb = self.data['Fig13'](s_b,asin_os)
152
+ i_minus_istall_sb = self.data['Fig13'](float(s_b), float(asin_os))
153
153
 
154
154
  if blade_inlet_angle<=90:
155
- i_minus_istall_basic = self.data['Fig13_alpha1'](s_b,asin_os)
155
+ i_minus_istall_basic = self.data['Fig13_alpha1'](float(s_b), float(asin_os))
156
156
  i_minus_istall = i_minus_istall_basic + i_minus_istall_sb # Eqn 6
157
157
  else:
158
- i_minus_istall_basic = self.data["Fig14_i-istall"](blade_inlet_angle,asin_os)
158
+ i_minus_istall_basic = self.data["Fig14_i-istall"](float(blade_inlet_angle), float(asin_os))
159
159
  i_minus_istall = i_minus_istall_basic + (1-(blade_inlet_angle - 90)/(90-asin_os)) * i_minus_istall_sb # Eqn 8
160
160
 
161
161
  imin = (i_plus_istall + Fi * (i_minus_istall))/(1+Fi) # type: ignore # Eqn 9
162
- N_pi = self.data['Fig10'](imin,incidence_ratio)
162
+ N_pi = self.data['Fig10'](float(imin), float(incidence_ratio))
163
163
  else:
164
164
  N_pi = 1 # No effect
165
165
 
166
166
  Xp = X_pb*N_pr*N_pi*N_pt + delta_X_pt + delta_Xp_se + delta_Xpm # Eqn 10
167
167
 
168
168
  # Secondary Loss
169
- Ns_hb = self.data['Fig17'](1/currentRow.aspect_ratio)
170
- x_sb = self.data['Fig18']((V_inlet/V)**2,s_b*Fl)
169
+ Ns_hb = self.data['Fig17'](float(1/currentRow.aspect_ratio))
170
+ x_sb = self.data['Fig18'](float((V_inlet/V)**2), float(s_b*Fl))
171
171
 
172
172
  Nsr = 1 # N_pr # I have no clue about this. Craig Cox doesn't describe. setting it to 1 for now.
173
173
  Xs = Nsr*Ns_hb*x_sb
@@ -192,4 +192,4 @@ class CraigCox(LossBaseClass):
192
192
  # According to Equation 3, Group 1 loss is an enthalpy loss Cp*T0. Need to convert to Pressure Loss
193
193
  eta_total = (upstream.T0.mean() - row.T0.mean())/(upstream.T0.mean()-(row.T0.mean()-T0_Loss))
194
194
  return eta_total
195
-
195
+
@@ -0,0 +1,166 @@
1
+ import pickle, os
2
+ from typing import Dict
3
+ from ...bladerow import BladeRow, sutherland
4
+ from ...lossinterp import LossInterp
5
+ from ...enums import RowType, LossType
6
+ import numpy as np
7
+ import pathlib
8
+ from ..losstype import LossBaseClass
9
+ import requests
10
+
11
+
12
+ def _mean_value(value):
13
+ """Return the mean of an array-like as a Python float."""
14
+ return float(np.asarray(value).mean())
15
+
16
+ class KackerOkapuu(LossBaseClass):
17
+ UseCFM:bool = False
18
+ def __init__(self,UseCFM:bool=False):
19
+ """KackerOkapuu model is an improvement to the Ainley Mathieson model.
20
+
21
+ Limitations:
22
+ - Doesn't factor incidence loss
23
+ - For steam turbines and impulse turbines
24
+
25
+ Args:
26
+ UseCFM (bool): Factor in supersonic drag rise. Authors state in AMDC loss that this is not accurate. It's a multplier to pressure loss.
27
+
28
+ Reference:
29
+ Kacker, S. C., and U. Okapuu. "A mean line prediction method for axial flow turbine efficiency." (1982): 111-119.
30
+
31
+ """
32
+ super().__init__(LossType.Pressure)
33
+ path = pathlib.Path(os.path.join(os.environ['TD3_HOME'],"kackerokapuu"+".pkl"))
34
+
35
+ if not path.exists():
36
+ url = "https://github.com/nasa/turbo-design/raw/main/references/Turbines/KackerOkapuu/kackerokapuu.pkl"
37
+ response = requests.get(url, stream=True)
38
+ with open(path.absolute(), mode="wb") as file:
39
+ for chunk in response.iter_content(chunk_size=10 * 1024):
40
+ file.write(chunk)
41
+
42
+ with open(path.absolute(),'rb') as f:
43
+ self.data = pickle.load(f) # type: ignore
44
+ self.UseCFM = UseCFM
45
+
46
+
47
+ def __call__(self,row:BladeRow, upstream:BladeRow) -> float:
48
+ """Kacker Okapuu is an updated version of Ainley Mathieson and Dunham Came. This tool uses the pressure loss definition.
49
+
50
+ Note:
51
+ All equation numbers are from the Kacker Okapuu paper
52
+
53
+ Reference:
54
+ Kacker, S. C., and U. Okapuu. "A mean line prediction method for axial flow turbine efficiency." (1982): 111-119.
55
+
56
+ Args:
57
+ upstream (BladeRow): Upstream blade row
58
+ row (BladeRow): downstream blade row
59
+
60
+ Returns:
61
+ float: Pressure Loss
62
+ """
63
+ # Get the Inlet incoming mach number relative to the blade
64
+ c = row.chord
65
+ b = row.axial_chord
66
+ if row.row_type == RowType.Stator:
67
+ beta1_rad = np.abs(_mean_value(np.radians(row.beta1_metal))) # Metal angle from fig 3
68
+ alpha1_rad = np.abs(_mean_value(row.alpha1)) # Flow angle
69
+ alpha2_rad = np.abs(_mean_value(row.alpha2)) # Flow angle at exit which is metal angle
70
+ beta2_rad = alpha2_rad
71
+ alpham_rad = (alpha1_rad + alpha2_rad)*0.5
72
+ M1 = _mean_value(upstream.M)
73
+ M2 = _mean_value(row.M)
74
+ h = 0
75
+ Rec = _mean_value(row.V*row.rho*row.chord / sutherland(row.T))
76
+
77
+ beta1_deg = np.abs(np.degrees(beta1_rad))
78
+ alpha2_deg = np.abs(np.degrees(alpha2_rad))
79
+ Yp_beta0 = self.data['Fig01_beta0'](float(row.pitch_to_chord), alpha2_deg) # when beta1 = 0
80
+ Yp_beta1_alpha2 = self.data['Fig02'](float(row.pitch_to_chord), alpha2_deg) # When beta1 = alpha2
81
+ t_max_c = self.data['Fig04'](beta1_deg+alpha2_deg)
82
+ else:
83
+ h = row.tip_clearance * (row.r[-1]-row.r[0])
84
+ alpha1_rad = np.abs(_mean_value(row.beta1))
85
+ beta1_rad = np.abs(_mean_value(np.radians(row.beta1_metal))) # metal angles are stored as degrees
86
+ beta2_rad = np.abs(_mean_value(np.radians(row.beta2_metal)))
87
+ alpha2_rad = np.abs(_mean_value(row.beta2))
88
+ alpham_rad = (beta1_rad + beta2_rad)*0.5
89
+ M1 = _mean_value(upstream.M_rel)
90
+ M2 = _mean_value(row.M_rel)
91
+ Rec = _mean_value(row.W*row.rho*row.chord / sutherland(row.T))
92
+
93
+ beta1_deg = np.abs(np.degrees(beta1_rad))
94
+ alpha2_deg = np.abs(np.degrees(beta2_rad))
95
+ Yp_beta0 = self.data['Fig01_beta0'](float(row.pitch_to_chord), alpha2_deg) # when beta1 = 0
96
+ Yp_beta1_alpha2 = self.data['Fig02'](float(row.pitch_to_chord), alpha2_deg) # When beta1 = alpha2
97
+ t_max_c = self.data['Fig04'](beta1_deg+alpha2_deg)
98
+
99
+ ratio = beta1_rad / alpha2_rad
100
+ Yp_amdc = (Yp_beta0 + np.abs(ratio) * ratio * (Yp_beta1_alpha2-Yp_beta0)) * ((t_max_c)/0.2)**(ratio) # Eqn 2, AMDC = Ainley Mathieson Dunham Came
101
+
102
+ # Shock Loss
103
+ if M1>=0.4: # You'll have imaginary numbers if M1<0.4
104
+ dP_q1_hub = 0.75*(M1-0.4)**1.75 # Eqn 4, this is at the hub
105
+ dP_q1_shock = row.r[-1]/row.r[0] * dP_q1_hub # Eqn 5
106
+ Y_shock = dP_q1_shock * upstream.P/row.P * (1-(1+(upstream.gamma-1)/2*M1**2))/(1-(1+(row.gamma-1)/2*M2**2)) # Eqn 6
107
+ Y_shock = _mean_value(Y_shock)
108
+ else:
109
+ Y_shock = 0
110
+
111
+ if M2 <= 0.2:
112
+ K1 = 1
113
+ else:
114
+ K1 = 1-1.25*(M2-0.2)
115
+ K2 = (M1/M2)**2
116
+ Kp = 1-K2*(1-K1)
117
+
118
+ if (M2>1) and (self.UseCFM is True):
119
+ CFM = 1+60*(M2-1)**2 # Eqn 9
120
+ else:
121
+ CFM = 1
122
+
123
+ Yp = 0.914 * (2/3*Yp_amdc*Kp + Y_shock) # Eqn 8 Subsonic regime
124
+ if M2>1:
125
+ Yp = Yp*CFM
126
+
127
+ f_ar = (1-0.25*np.sqrt(2-h/c)) / (h/c) if h/c<=2 else 1/(h/c)
128
+ alpham = np.arctan(0.5*(np.tan(alpha1_rad) - np.tan(alpha2_rad)))
129
+ Cl_sc = 2*(np.tan(alpha1_rad)+np.tan(alpha2_rad))*np.cos(alpham)
130
+ Ys_amdc = 0.0334 *f_ar *np.cos(alpha2_rad)/np.cos(beta1_rad) * (Cl_sc)**2 * np.cos(alpha2_rad)**2 / np.cos(alpham)**3
131
+ # Secondary Loss
132
+ if h>0: # h is calculated from tip clearance. When h is 0 there is no tip clearance
133
+ K3 = 1/(h/(b))**2 # Fig 13, it's actually bx in the picture which is the axial chord; h is 0 this causes nan
134
+ Ks = 1-K3*(1-Kp) # Eqn 15
135
+ Ys = 1.2*Ys_amdc*Ks # Eqn 16
136
+ else:
137
+ K3 = 0
138
+ Ks = 0
139
+ Ys = 0
140
+
141
+ # Trailing Edge
142
+ if np.abs(beta1_deg-np.degrees(beta2_rad))<5: # impulse turbine the inlet and exit angles are the same
143
+ delta_phi2 = self.data['Fig14_Impulse'](float(row.te_pitch*row.pitch / row.throat))
144
+ else:
145
+ delta_phi2 = self.data['Fig14_Axial_Entry'](float(row.te_pitch*row.pitch / row.throat))
146
+
147
+ Ytet = (1-(row.gamma-1)/2 * M2**2 * (1/(1-delta_phi2)-1)) **(-row.gamma/(row.gamma-1)) - 1 # Equation 18
148
+ Ytet = Ytet / (1-(1+(row.gamma-1)/2*M2**2)**(-row.gamma/(row.gamma-1)))
149
+
150
+ # Tip Clearance
151
+ if h > 0:
152
+ kprime = row.tip_clearance/(3)**0.42 # Number of seals
153
+ Ytc = 0.37*c/h * (kprime/c)**0.78 * Cl_sc**2 * np.cos(alpha2_rad)**2 / np.cos(alpham)**3
154
+ else:
155
+ Ytc = 0
156
+
157
+ if Rec <= 2E5:
158
+ f_re = (Rec/2E5)**-0.4
159
+ elif Rec<1E6:
160
+ f_re = 1
161
+ else:
162
+ f_re = (Rec/1E6)**-0.2
163
+
164
+ Yt = Yp*f_re + Ys + Ytet + Ytc
165
+ return Yt
166
+
@@ -45,21 +45,21 @@ class Traupel(LossBaseClass):
45
45
 
46
46
  if row.row_type == RowType.Rotor:
47
47
  turning = np.abs(np.degrees(upstream.beta2-row.beta2).mean())
48
- F = self.data['Fig06']((upstream.W/row.W).mean(),turning) # Inlet velocity
48
+ F = self.data['Fig06'](float((upstream.W/row.W).mean()), float(turning)) # Inlet velocity
49
49
  else:
50
50
  turning = np.abs(np.degrees(upstream.alpha2-row.alpha2).mean())
51
- F = self.data['Fig06']((upstream.V/row.V).mean(),turning) # Inlet velocity
51
+ F = self.data['Fig06'](float((upstream.V/row.V).mean()), float(turning)) # Inlet velocity
52
52
 
53
- H = self.data['Fig07'](alpha1-beta2,alpha2-beta3)
53
+ H = self.data['Fig07'](float(alpha1-beta2), float(alpha2-beta3))
54
54
 
55
55
  zeta_s = F*g/h_stator # (h1-h1s)/(0.5*c1s**2) # no idea what h1s or h2s is
56
56
  zeta_r = F*g/h_rotor # (h2-h2s)/(0.5*w2s**2)
57
- x_p_stator = self.data['Fig01'](alpha1,alpha2) # not sure if this is the right figure
58
- x_p_rotor = self.data['Fig01'](beta2,beta3) # not sure if this is the right figure
59
- zeta_p_stator = self.data['Fig02'](alpha1,alpha2)
60
- x_m_stator = self.data['Fig03_0'](upstream.M)
61
- zeta_p_rotor = self.data['Fig02'](beta2,beta3)
62
- x_m_rotor = self.data['Fig03_0'](row.M_rel)
57
+ x_p_stator = self.data['Fig01'](float(alpha1), float(alpha2)) # not sure if this is the right figure
58
+ x_p_rotor = self.data['Fig01'](float(beta2), float(beta3)) # not sure if this is the right figure
59
+ zeta_p_stator = self.data['Fig02'](float(alpha1), float(alpha2))
60
+ x_m_stator = self.data['Fig03_0'](float(np.mean(upstream.M)))
61
+ zeta_p_rotor = self.data['Fig02'](float(beta2), float(beta3))
62
+ x_m_rotor = self.data['Fig03_0'](float(np.mean(row.M_rel)))
63
63
 
64
64
 
65
65
  e_te = upstream.te_pitch * g
@@ -67,10 +67,10 @@ class Traupel(LossBaseClass):
67
67
  ssen_alpha2 = e_te/o # Thickness of Trailing edge divide by throat
68
68
  ssen_beta2 = row.te_pitch*g / row.throat
69
69
 
70
- x_delta_stator = self.data['Fig05'](ssen_alpha2,alpha2)
71
- zeta_delta_stator = self.data['Fig04'](ssen_alpha2,alpha2)
72
- x_delta_rotor = self.data['Fig05'](ssen_beta2,beta3)
73
- zeta_delta_rotor = self.data['Fig04'](ssen_beta2,beta3)
70
+ x_delta_stator = self.data['Fig05'](float(ssen_alpha2), float(alpha2))
71
+ zeta_delta_stator = self.data['Fig04'](float(ssen_alpha2), float(alpha2))
72
+ x_delta_rotor = self.data['Fig05'](float(ssen_beta2), float(beta3))
73
+ zeta_delta_rotor = self.data['Fig04'](float(ssen_beta2), float(beta3))
74
74
 
75
75
  Dm = 2* (upstream.r[-1] + upstream.r[0])/2 # Is this the mean diameter? I dont know
76
76
  zeta_f = 0.5 * (h_stator/Dm)**2
@@ -85,7 +85,7 @@ class Traupel(LossBaseClass):
85
85
  if row.row_type == RowType.Stator:
86
86
  zeta_cl = 0
87
87
  else:
88
- zeta_cl = self.data['Fig08'](row.tip_clearance) # For simplicity assume unshrouded blade
88
+ zeta_cl = self.data['Fig08'](float(row.tip_clearance)) # For simplicity assume unshrouded blade
89
89
 
90
90
  zeta_z = 0 # Do not factor this in, a bit complicated
91
91
  # 1 - (internal) - (external)
@@ -97,4 +97,4 @@ class Traupel(LossBaseClass):
97
97
 
98
98
 
99
99
 
100
-
100
+
@@ -6,7 +6,8 @@ from pyturbo.helper import line2D
6
6
  from .enums import PassageType
7
7
  from scipy.optimize import minimize_scalar
8
8
  from findiff import FinDiff
9
- from pyturbo.helper import convert_to_ndarray,xr_to_mprime
9
+ from pyturbo.helper import convert_to_ndarray, xr_to_mprime
10
+
10
11
  import matplotlib.pyplot as plt
11
12
 
12
13
  class Passage:
@@ -50,10 +51,13 @@ class Passage:
50
51
  self.xshroud = PchipInterpolator(hub_arc_len/hub_arc_len[-1],xshroud)
51
52
  self.rshroud = PchipInterpolator(hub_arc_len/hub_arc_len[-1],rshroud)
52
53
 
53
- self.n = len(xhub)
54
+ if len(xhub) < 10:
55
+ self.n = 10
56
+ else:
57
+ self.n = len(xhub)
54
58
 
55
- self.xhub_pts = convert_to_ndarray(xhub)
56
- self.rhub_pts = convert_to_ndarray(rhub)
59
+ self.xhub_pts = convert_to_ndarray(xhub) # type: ignore
60
+ self.rhub_pts = convert_to_ndarray(rhub) # type: ignore
57
61
  self.xshroud_pts = convert_to_ndarray(xshroud)
58
62
  self.rshroud_pts = convert_to_ndarray(rshroud)
59
63
 
@@ -114,6 +118,7 @@ class Passage:
114
118
 
115
119
  d_dx = FinDiff(0,x_streamline[indices[0]:indices[-1]],1)
116
120
  d2_dx2 = FinDiff(0,x_streamline[indices[0]:indices[-1]],2)
121
+
117
122
  dr_dx = d_dx(r_streamline[indices[0]:indices[-1]])
118
123
  d2r_dx2 = d2_dx2(r_streamline[indices[0]:indices[-1]])
119
124
 
@@ -271,6 +271,26 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
271
271
  row (BladeRow): Rotor Row
272
272
  upstream (BladeRow): Stator Row or Rotor Row
273
273
  """
274
+ def _log_rotor_failure(reason:str):
275
+ def _fmt(val):
276
+ try:
277
+ return np.array2string(np.asarray(val), precision=5)
278
+ except Exception:
279
+ return str(val)
280
+
281
+ print(f"[RotorCalc] Failure detected: {reason}")
282
+ print(f" row.T0R: {_fmt(row.T0R)}")
283
+ print(f" row.T: {_fmt(row.T)}")
284
+ print(f" row.W: {_fmt(row.W)}")
285
+ print(f" row.M: {_fmt(getattr(row,'M', np.nan))}")
286
+ print(f" row.M_rel: {_fmt(getattr(row,'M_rel', np.nan))}")
287
+ print(f" row.Yp: {_fmt(getattr(row,'Yp', np.nan))}")
288
+ if np.any(row.T >= row.T0R):
289
+ print(" Note: T should be less than T0R.")
290
+ yp_val = getattr(row,'Yp', None)
291
+ if yp_val is not None and np.any(yp_val > 0.3):
292
+ print(" Note: row.Yp exceeded 0.3 which may indicate an issue with the design or loss model.")
293
+
274
294
  row.P0_stator_inlet = upstream.P0_stator_inlet
275
295
  ## P0_P is assumed
276
296
  # row.P = row.P0_stator_inlet*1/row.P0_P
@@ -303,9 +323,18 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
303
323
  row.T = (row.T0R/T0R_T) # Exit static temperature
304
324
  if calculate_vm: # Calculates the T0 at the exit
305
325
  row.W = np.sqrt(2*row.Cp*(row.T0R-row.T)) #! nan popups here a lot for radial machines
306
- if np.isnan(np.sum(row.W)):
326
+ nan_in_velocity = np.isnan(np.sum(row.W))
327
+ temp_issue = np.any(row.T >= row.T0R)
328
+ high_loss = np.any(getattr(row,'Yp',0) > 0.3)
329
+ if nan_in_velocity:
307
330
  # Need to adjust T
308
- raise ValueError(f'nan detected: check flow path. Turbine inlet cut should be horizontal')
331
+ reason = "nan detected in relative velocity"
332
+ if temp_issue:
333
+ reason += "; T >= T0R shouldn't happen because of T-s diagram'"
334
+ if high_loss:
335
+ reason += "; Yp > 0.3 This could be a problem with the loss model;"
336
+ _log_rotor_failure(reason)
337
+ raise ValueError(f'nan detected')
309
338
  row.Vr = row.W*np.sin(row.phi)
310
339
  row.Vm = row.W*np.cos(row.beta2)
311
340
  row.Wt = row.W*np.sin(row.beta2)
@@ -378,4 +407,4 @@ def inlet_calc(row:BladeRow):
378
407
  raise ValueError(f"High inlet mach can lead to errors iter:{iter} Mach:{avg_mach}")
379
408
 
380
409
  if np.mean(row.M)<0.01:
381
- print(f"Unusually slow flow:{iter} Mach:{avg_mach}")
410
+ print(f"Unusually slow flow:{iter} Mach:{avg_mach}")
@@ -1 +0,0 @@
1
- from .losstype import LossType, LossBaseClass
@@ -1,25 +0,0 @@
1
- from typing import Any, Dict
2
- from ..lossinterp import LossInterp
3
- import os
4
- from ..enums import LossType
5
-
6
- class LossBaseClass:
7
- data: Dict[str,LossInterp]
8
- _loss_type:LossType
9
-
10
- def __init__(self,lossType:LossType):
11
- # Make the environment directory
12
- default_home = os.path.join(os.path.expanduser("~"), ".cache")
13
- os.environ['TD3_HOME'] = os.path.join(default_home,'TD3_LossModels')
14
- os.makedirs(os.environ['TD3_HOME'],exist_ok=True)
15
-
16
- self._loss_type = lossType
17
-
18
- def __call__(self, row:Any, upstream:Any):
19
- raise Exception("Method needs to be overridden")
20
-
21
-
22
-
23
- @property
24
- def loss_type(self):
25
- return self._loss_type
@@ -1,129 +0,0 @@
1
- import pickle, os
2
- from typing import Dict
3
- from ...bladerow import BladeRow, sutherland
4
- from ...lossinterp import LossInterp
5
- from ...enums import RowType, LossType
6
- import numpy as np
7
- import pathlib
8
- from ..losstype import LossBaseClass
9
- import requests
10
-
11
- class KackerOkapuu(LossBaseClass):
12
-
13
- def __init__(self):
14
- """KackerOkapuu model is an improvement to the Ainley Mathieson model.
15
-
16
- Limitations:
17
- - Doesn't factor incidence loss
18
- - For steam turbines and impulse turbines
19
-
20
- Reference:
21
- Kacker, S. C., and U. Okapuu. "A mean line prediction method for axial flow turbine efficiency." (1982): 111-119.
22
-
23
- """
24
- super().__init__(LossType.Pressure)
25
- path = pathlib.Path(os.path.join(os.environ['TD3_HOME'],"kackerokapuu"+".pkl"))
26
-
27
- if not path.exists():
28
- url = "https://github.com/nasa/turbo-design/raw/main/references/Turbines/KackerOkapuu/kackerokapuu.pkl"
29
- response = requests.get(url, stream=True)
30
- with open(path.absolute(), mode="wb") as file:
31
- for chunk in response.iter_content(chunk_size=10 * 1024):
32
- file.write(chunk)
33
-
34
- with open(path.absolute(),'rb') as f:
35
- self.data = pickle.load(f) # type: ignore
36
-
37
-
38
-
39
- def __call__(self,row:BladeRow, upstream:BladeRow) -> float:
40
- """Kacker Okapuu is an updated version of Ainley Mathieson and Dunham Came. This tool uses the pressure loss definition.
41
-
42
- Note:
43
- All equation numbers are from the Kacker Okapuu paper
44
-
45
- Reference:
46
- Kacker, S. C., and U. Okapuu. "A mean line prediction method for axial flow turbine efficiency." (1982): 111-119.
47
-
48
- Args:
49
- upstream (BladeRow): Upstream blade row
50
- row (BladeRow): downstream blade row
51
-
52
- Returns:
53
- float: Pressure Loss
54
- """
55
- if upstream.row_type == RowType.Stator:
56
- M1 = upstream.M
57
- else:
58
- M1 = upstream.M_rel
59
-
60
- c = row.chord
61
- b = row.axial_chord
62
- if row.row_type == RowType.Stator:
63
- alpha1 = np.degrees(row.alpha1)
64
- beta1 = np.degrees(row.beta1_metal)
65
- alpha2 = np.degrees(row.alpha2)
66
- M2 = row.M
67
- h = 0
68
- Rec = row.V*row.rho*row.chord / sutherland(row.T)
69
- else:
70
- h = row.tip_clearance * (row.r[-1]-row.r[0])
71
- alpha1 = np.degrees(row.beta1)
72
- beta1 = row.beta1_metal
73
- alpha2 = np.degrees(row.beta2)
74
- M2 = row.M_rel
75
- Rec = row.W*row.rho*row.chord / sutherland(row.T)
76
-
77
- Yp_beta0 = self.data['Fig01'](row.pitch_to_chord, alpha2)
78
- Yp_beta1_alpha2 = self.data['Fig02'](row.pitch_to_chord, alpha2)
79
- t_max_c = self.data['Fig04'](np.abs(beta1)+np.abs(alpha2))
80
-
81
- Yp_amdc = (Yp_beta0 + np.abs(beta1/alpha2) *beta1/alpha2 * (Yp_beta1_alpha2-Yp_beta0)) * ((t_max_c)/0.2)**(beta1/alpha2) # Eqn 2, AMDC = Ainley Mathieson Dunham Came
82
-
83
- # Shock Loss
84
- dP_q1_hub = 0.75*(M1-0.4)**1.75 # Eqn 4, this is at the hub
85
- dP_q1_shock = row.r[-1]/row.r[0] * dP_q1_hub # Eqn 5
86
- Y_shock = dP_q1_shock * upstream.P/row.P * (1-(1+(upstream.gamma-1)/2*M1**2))/(1-(1+(row.gamma-1)/2*M2**2)) # Eqn 6
87
-
88
- K1 = self.data['Fig08_K1'](M2)
89
- K2 = (M1/M2)**2
90
- Kp = 1-K2*(1-K1)
91
-
92
- CFM = 1+60*(M2-1)**2 # Eqn 9
93
-
94
- Yp = 0.914 * (2/3*Yp_amdc *Kp + Y_shock) # Eqn 8 Subsonic regime
95
- if M2>1:
96
- Yp = Yp*CFM
97
-
98
- f_ar = (1-0.25*np.sqrt(2-h/c)) / (h/c) if h/c<=2 else 1/(h/c)
99
- alpham = np.arctan(0.5*(np.tan(alpha1) - np.tan(alpha2)))
100
- Cl_sc = 2*(np.tan(alpha1)+np.tan(alpha2))*np.cos(alpham)
101
- Ys_amdc = 0.0334 *f_ar *np.cos(alpha2)/np.cos(beta1) * (Cl_sc)**2 * np.cos(alpha2)**2 / np.cos(alpham)**3
102
- # Secondary Loss
103
- K3 = 1/(h/(b))**2 # Fig 13, it's actually bx in the picture which is the axial chord
104
- Ks = 1-K3*(1-Kp) # Eqn 15
105
- Ys = 1.2*Ys_amdc*Ks # Eqn 16
106
-
107
- # Trailing Edge
108
- if np.abs(alpha1-alpha2)<5:
109
- delta_phi2 = self.data['Fig14_Impulse'](row.te_pitch*row.pitch / row.throat)
110
- else:
111
- delta_phi2 = self.data['Fig14_Axial_Entry'](row.te_pitch*row.pitch / row.throat)
112
-
113
- Ytet = (1-(row.gamma-1)/2 - M2**2 * (1/(1-delta_phi2)-1))**(-row.gamma/(row.gamma-1))-1
114
- Ytet = Ytet / (1-(1+(row.gamma-1)/2*M2**2)**(-row.gamma/(row.gamma-1)))
115
-
116
- # Tip Clearance
117
- kprime = row.tip_clearance/(3)**0.42 # Number of seals
118
- Ytc = 0.37*c/h * (kprime/c)**0.78 * Cl_sc**2 * np.cos(alpha2)**2 / np.cos(alpham)**3
119
-
120
- if Rec <= 2E5:
121
- f_re = (Rec/2E5)**-0.4
122
- elif Rec<1E6:
123
- f_re = 1
124
- else:
125
- f_re = (Rec/1E6)**-0.2
126
-
127
- Yt = Yp*f_re + Ys + Ytet + Ytc
128
- return Yt
129
-