turbo-design 1.3.8__py3-none-any.whl → 1.3.10__py3-none-any.whl

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.
Files changed (48) hide show
  1. {turbo_design-1.3.8.dist-info → turbo_design-1.3.10.dist-info}/METADATA +2 -1
  2. turbo_design-1.3.10.dist-info/RECORD +46 -0
  3. {turbo_design-1.3.8.dist-info → turbo_design-1.3.10.dist-info}/WHEEL +1 -1
  4. turbodesign/__init__.py +57 -4
  5. turbodesign/agf.py +346 -0
  6. turbodesign/arrayfuncs.py +31 -1
  7. turbodesign/bladerow.py +238 -155
  8. turbodesign/compressor_math.py +386 -0
  9. turbodesign/compressor_spool.py +941 -0
  10. turbodesign/coolant.py +18 -6
  11. turbodesign/deviation/__init__.py +5 -0
  12. turbodesign/deviation/axial_compressor.py +3 -0
  13. turbodesign/deviation/carter_deviation.py +79 -0
  14. turbodesign/deviation/deviation_base.py +20 -0
  15. turbodesign/deviation/fixed_deviation.py +42 -0
  16. turbodesign/enums.py +5 -6
  17. turbodesign/flow_math.py +158 -0
  18. turbodesign/inlet.py +126 -56
  19. turbodesign/isentropic.py +59 -15
  20. turbodesign/loss/__init__.py +3 -1
  21. turbodesign/loss/compressor/OTAC_README.md +39 -0
  22. turbodesign/loss/compressor/__init__.py +54 -0
  23. turbodesign/loss/compressor/diffusion.py +61 -0
  24. turbodesign/loss/compressor/lieblein.py +1 -0
  25. turbodesign/loss/compressor/otac.py +799 -0
  26. turbodesign/loss/compressor/references/schobeiri-2012-shock-loss-model-for-transonic-and-supersonic-axial-compressors-with-curved-blades.pdf +0 -0
  27. turbodesign/loss/fixedpolytropic.py +27 -0
  28. turbodesign/loss/fixedpressureloss.py +30 -0
  29. turbodesign/loss/losstype.py +2 -30
  30. turbodesign/loss/turbine/TD2.py +25 -29
  31. turbodesign/loss/turbine/__init__.py +0 -1
  32. turbodesign/loss/turbine/ainleymathieson.py +6 -5
  33. turbodesign/loss/turbine/craigcox.py +6 -5
  34. turbodesign/loss/turbine/fixedefficiency.py +8 -7
  35. turbodesign/loss/turbine/kackerokapuu.py +7 -5
  36. turbodesign/loss/turbine/traupel.py +17 -16
  37. turbodesign/outlet.py +81 -22
  38. turbodesign/passage.py +98 -63
  39. turbodesign/row_factory.py +129 -0
  40. turbodesign/solve_radeq.py +9 -10
  41. turbodesign/{td_math.py → turbine_math.py} +144 -185
  42. turbodesign/turbine_spool.py +1219 -0
  43. turbo_design-1.3.8.dist-info/RECORD +0 -33
  44. turbodesign/compressorspool.py +0 -60
  45. turbodesign/loss/turbine/fixedpressureloss.py +0 -25
  46. turbodesign/rotor.py +0 -38
  47. turbodesign/spool.py +0 -317
  48. turbodesign/turbinespool.py +0 -543
turbodesign/isentropic.py CHANGED
@@ -1,8 +1,19 @@
1
+ from typing import Union
1
2
  import numpy as np
3
+ import numpy.typing as npt
2
4
  import math
3
5
 
6
+ ArrayLike = Union[float, npt.NDArray[np.float64]]
4
7
 
5
- def IsenP(M:np.ndarray,gamma:float) -> float:
8
+
9
+ def _maybe_return_scalar(result: npt.NDArray[np.float64], *inputs: object) -> ArrayLike:
10
+ """Return a float when all driving inputs were scalar, else an ndarray."""
11
+ if all(np.isscalar(inp) for inp in inputs):
12
+ return float(np.asarray(result))
13
+ return np.asarray(result, dtype=float)
14
+
15
+
16
+ def IsenP(M:ArrayLike,gamma:float) -> ArrayLike:
6
17
  """Computes the ratio P0/Ps
7
18
 
8
19
  Args:
@@ -12,10 +23,12 @@ def IsenP(M:np.ndarray,gamma:float) -> float:
12
23
  Returns:
13
24
  float: P0/P ratio
14
25
  """
15
- return np.power((1+(gamma-1)/2.0 * M*M),gamma/(gamma-1))
26
+ M_arr = np.asarray(M, dtype=float)
27
+ result = np.power((1+(gamma-1)/2.0 * M_arr*M_arr),gamma/(gamma-1))
28
+ return _maybe_return_scalar(result, M)
16
29
 
17
30
 
18
- def FindMachP0P(P0_P:np.ndarray,gamma:float) -> float:
31
+ def FindMachP0P(P0_P:ArrayLike,gamma:float) -> ArrayLike:
19
32
  """Finds the mach number given a P0/P ratio
20
33
 
21
34
  Args:
@@ -26,14 +39,14 @@ def FindMachP0P(P0_P:np.ndarray,gamma:float) -> float:
26
39
  float: [description]
27
40
  """
28
41
  n = (gamma-1)/gamma
29
- c = 2.0/(gamma-1) * (np.power(P0_P,n) - 1.0)
30
-
42
+ P0_P_arr = np.asarray(P0_P, dtype=float)
43
+ c = 2.0/(gamma-1) * (np.power(P0_P_arr,n) - 1.0)
31
44
  M = np.sqrt(c)
32
- return M # Subsonic and supersonic solution
45
+ return _maybe_return_scalar(M, P0_P)
33
46
 
34
47
 
35
48
 
36
- def IsenT(M:np.ndarray,gamma:float) -> float:
49
+ def IsenT(M:ArrayLike,gamma:float) -> ArrayLike:
37
50
  """Computes T0/Ts
38
51
 
39
52
  Args:
@@ -43,10 +56,12 @@ def IsenT(M:np.ndarray,gamma:float) -> float:
43
56
  Returns:
44
57
  float: Ratio of T0/Ts
45
58
  """
46
- return (1.0+(gamma-1.0)/2.0 *M*M)
59
+ M_arr = np.asarray(M, dtype=float)
60
+ result = (1.0+(gamma-1.0)/2.0 *M_arr*M_arr)
61
+ return _maybe_return_scalar(result, M)
47
62
 
48
63
 
49
- def A_As(M:np.ndarray,gamma:float) -> float:
64
+ def A_As(M:ArrayLike,gamma:float) -> ArrayLike:
50
65
  """Computes the ratio of Area to Throat Area give a given mach number and gamma
51
66
 
52
67
  Args:
@@ -58,11 +73,13 @@ def A_As(M:np.ndarray,gamma:float) -> float:
58
73
  """
59
74
  a = (gamma+1.0)/(2.0*(gamma-1.0))
60
75
  temp1 = np.power((gamma+1.0)/2.0,a)
61
- temp2 = np.power((1+(gamma-1)/2*M*M),-a)/M
62
- return temp1*temp2
76
+ M_arr = np.asarray(M, dtype=float)
77
+ temp2 = np.power((1+(gamma-1)/2*M_arr*M_arr),-a)/M_arr
78
+ result = temp1*temp2
79
+ return _maybe_return_scalar(result, M)
63
80
 
64
81
 
65
- def Massflow(P0:float,T0:float,A:float,M:float,gamma:float,R:float=287):
82
+ def Massflow(P0:ArrayLike,T0:ArrayLike,A:ArrayLike,M:ArrayLike,gamma:float,R:float=287) -> ArrayLike:
66
83
  """Massflow rate calculation
67
84
 
68
85
  Args:
@@ -76,7 +93,34 @@ def Massflow(P0:float,T0:float,A:float,M:float,gamma:float,R:float=287):
76
93
  Returns:
77
94
  float: Nusselt Number
78
95
  """
79
- mdot = A * P0/np.sqrt(T0) * np.sqrt(gamma/R) * M \
80
- *np.power(1.0+(gamma-1.0)/2.0 * M*M, -(gamma+1.0)/(2.0*(gamma-1.0)))
96
+ P0_arr = np.asarray(P0, dtype=float)
97
+ T0_arr = np.asarray(T0, dtype=float)
98
+ A_arr = np.asarray(A, dtype=float)
99
+ M_arr = np.asarray(M, dtype=float)
100
+ gamma_val = float(gamma)
101
+ R_val = float(R)
102
+ mdot = A_arr * P0_arr/np.sqrt(T0_arr) * np.sqrt(gamma_val/R_val) * M_arr \
103
+ *np.power(1.0+(gamma_val-1.0)/2.0 * M_arr*M_arr, -(gamma_val+1.0)/(2.0*(gamma_val-1.0)))
104
+ return _maybe_return_scalar(mdot, P0, T0, A, M)
105
+
106
+
107
+ def solve_for_mach(M: float, massflow: float, P0: float, T0: float, area: float, gamma: float, R: float) -> float:
108
+ """Residual between desired and estimated massflow for a guessed Mach number.
109
+
110
+ Args:
111
+ M (float): Mach number guess (dimensionless).
112
+ massflow (float): Target massflow [kg/s].
113
+ P0 (float): Total pressure [Pa].
114
+ T0 (float): Total temperature [K].
115
+ area (float): Flow area [m^2].
116
+ gamma (float): Specific heat ratio Cp/Cv [-].
117
+ R (float): Gas constant [J/(kg·K)].
118
+
119
+ Returns:
120
+ float: Absolute massflow residual [kg/s].
121
+ """
122
+ expo = -(gamma + 1.0) / (2.0 * (gamma - 1.0))
81
123
 
82
- return mdot
124
+ estimate = area* P0/np.sqrt(T0)*np.sqrt(gamma / R)*M*np.power(1.0 + (gamma - 1.0) / 2.0 * M * M, expo)
125
+ residual = np.abs(massflow - estimate)
126
+ return residual
@@ -1 +1,3 @@
1
- from .losstype import LossType, LossBaseClass, CompositeLossModel
1
+ from .losstype import LossType, LossBaseClass
2
+ from .fixedpressureloss import FixedPressureLoss
3
+ from .fixedpolytropic import FixedPolytropicEfficiency
@@ -0,0 +1,39 @@
1
+ OTAC compressor loss models
2
+ ===========================
3
+
4
+ The classes in `otac.py` mirror the legacy OTAC `*.int` loss models. They are
5
+ currently placeholders that return zeros and emit a warning so the loss API can
6
+ be wired without failing.
7
+
8
+ When you translate an OTAC model, these mappings are a good starting point:
9
+
10
+ - `Fl_IR` → upstream `BladeRow` (inlet to the current row)
11
+ - `Fl_OR` → current `BladeRow` (outlet of the current row)
12
+ - `V`, `W`, `U`, `Vz`, `Vr`, `Vtheta` → `row.V`, `row.W`, `row.U`, `row.Vx`, `row.Vr`, `row.Vt`
13
+ - `alpha`, `beta` (flow angles) → `row.alpha1/alpha2` (absolute), `row.beta1/beta2` (relative)
14
+ - `ts`, `Tt`, `T` → static temperature `row.T`, total temperature `row.T0`
15
+ - `Ps`, `Pt`, `P` → static pressure `row.P`, total pressure `row.P0`
16
+ - `rho`, `rhos`, `rhot` → density `row.rho`
17
+ - `mu` → dynamic viscosity `row.mu`
18
+ - `ht` (total enthalpy) → `row.Cp * row.T0`
19
+ - `MN`, `M` → Mach number `row.M` (absolute) or `row.M_rel` (relative)
20
+ - `radius`, `radiusTipInlet`, `radiusExit` → `row.r` entries (`row.r[0]` hub, `row.r[-1]` tip)
21
+ - `bwidth`, `pitch`, `chord`, `throat` → `row.pitch`, `row.chord`, `row.throat`
22
+
23
+ Most OTAC models assume enthalpy loss; set the loss type accordingly in the
24
+ class `__init__`. If a model clamps the loss (e.g., to 25–50% of the available
25
+ enthalpy rise), keep those guards to avoid runaway penalties.
26
+
27
+ Recommended workflow:
28
+
29
+ 1. Pick a model, open the matching `otac/*.int` file, and translate `calculate`
30
+ into a vectorized NumPy computation inside the corresponding class in
31
+ `otac.py`.
32
+ 2. Replace the base class with `LossBaseClass` directly once implemented
33
+ (remove `_OTACStub` inheritance) and drop the warning.
34
+ 3. Use `row`/`upstream` attributes from `BladeRow` for inputs; add any new
35
+ geometry fields you need to `BladeRow` with sensible defaults.
36
+ 4. Return an array shaped like `row.r` (use `np.full_like(row.r, value)` when
37
+ the loss is a scalar).
38
+
39
+ This file is intentionally brief; keep notes here as you refine the mappings.
@@ -1 +1,55 @@
1
+ from .otac import (
2
+ AxialCompressorAungier,
3
+ AxialCompressorEntropy,
4
+ AxialCompressorWrightMiller,
5
+ AxialTurbineAinleyMathiesonOTAC,
6
+ AxialTurbineKackerOkapuuOTAC,
7
+ DiffuserVanelessStanitz,
8
+ ImpellerBladeLoadingAungier,
9
+ ImpellerBladeLoadingCoppage,
10
+ ImpellerClearanceJansen,
11
+ ImpellerDiscFrictionDaily,
12
+ ImpellerIncidenceAungier,
13
+ ImpellerIncidenceConrad,
14
+ ImpellerLeakageAungier,
15
+ ImpellerMixingAungier,
16
+ ImpellerMixingJohnston,
17
+ ImpellerPrescribed,
18
+ ImpellerRecirculationAungier,
19
+ ImpellerRecirculationOh,
20
+ ImpellerSkinFrictionCoppage,
21
+ ImpellerSkinFrictionJansen,
22
+ ImpellerVarious,
23
+ NASA23B20,
24
+ NASA74A,
25
+ RadialInput,
26
+ )
27
+ from .diffusion import DiffusionLoss
1
28
 
29
+ __all__ = [
30
+ "AxialCompressorAungier",
31
+ "AxialCompressorEntropy",
32
+ "AxialCompressorWrightMiller",
33
+ "AxialTurbineAinleyMathiesonOTAC",
34
+ "AxialTurbineKackerOkapuuOTAC",
35
+ "DiffuserVanelessStanitz",
36
+ "ImpellerBladeLoadingAungier",
37
+ "ImpellerBladeLoadingCoppage",
38
+ "ImpellerClearanceJansen",
39
+ "ImpellerDiscFrictionDaily",
40
+ "ImpellerIncidenceAungier",
41
+ "ImpellerIncidenceConrad",
42
+ "ImpellerLeakageAungier",
43
+ "ImpellerMixingAungier",
44
+ "ImpellerMixingJohnston",
45
+ "ImpellerPrescribed",
46
+ "ImpellerRecirculationAungier",
47
+ "ImpellerRecirculationOh",
48
+ "ImpellerSkinFrictionCoppage",
49
+ "ImpellerSkinFrictionJansen",
50
+ "ImpellerVarious",
51
+ "NASA23B20",
52
+ "NASA74A",
53
+ "RadialInput",
54
+ "DiffusionLoss",
55
+ ]
@@ -0,0 +1,61 @@
1
+ from typing import Optional, TYPE_CHECKING
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+
6
+ from ..losstype import LossBaseClass
7
+ from ...enums import LossType, RowType
8
+
9
+ if TYPE_CHECKING:
10
+ from ...bladerow import BladeRow # for type hints only
11
+
12
+
13
+ class DiffusionLoss(LossBaseClass):
14
+ """Pressure-loss model based on diffusion factor.
15
+
16
+ Computes a spanwise diffusion factor and maps it to a pressure-loss
17
+ coefficient Yp via a simple linear ramp above a threshold.
18
+ """
19
+
20
+ def __init__(self, df_limit: float = 0.45, df_knee: float = 0.6, df_max: float = 0.9, yp_at_max: float = 0.08):
21
+ """
22
+ Args:
23
+ df_limit: Diffusion factor below which loss is negligible.
24
+ df_knee: Diffusion factor where loss starts ramping up noticeably.
25
+ df_max: Diffusion factor at which Yp reaches ``yp_at_max``.
26
+ yp_at_max: Pressure-loss coefficient when ``df_max`` is reached.
27
+ """
28
+ super().__init__(LossType.Pressure)
29
+ self.df_limit = df_limit
30
+ self.df_knee = df_knee
31
+ self.df_max = df_max
32
+ self.yp_at_max = yp_at_max
33
+
34
+ def __call__(self, row: "BladeRow", upstream: "BladeRow") -> npt.NDArray:
35
+ """Return pressure-loss coefficient Yp based on diffusion factor."""
36
+ # Choose absolute vs relative velocities depending on row type
37
+ if row.row_type == RowType.Rotor:
38
+ V1 = upstream.W
39
+ V2 = row.W
40
+ Vt1 = upstream.Wt
41
+ Vt2 = row.Wt
42
+ U = upstream.U
43
+ else:
44
+ V1 = upstream.V
45
+ V2 = row.V
46
+ Vt1 = upstream.Vt
47
+ Vt2 = row.Vt
48
+ U = 0.0
49
+
50
+ V1_mag = np.maximum(np.abs(V1), 1e-6)
51
+ V2_mag = np.abs(V2)
52
+ dVt = Vt2 - Vt1
53
+
54
+ df = 1.0 - V2_mag / V1_mag + dVt / np.maximum(np.abs(U), 1e-6)
55
+
56
+ # Map diffusion factor to Yp
57
+ Yp = np.zeros_like(row.percent_hub_shroud, dtype=float)
58
+ ramp = (df - self.df_limit) / max(self.df_knee - self.df_limit, 1e-6)
59
+ ramp = np.clip(ramp, 0.0, 1.0)
60
+ Yp = ramp * (self.yp_at_max * np.clip((df - self.df_limit) / max(self.df_max - self.df_limit, 1e-6), 0.0, 1.0))
61
+ return Yp
@@ -0,0 +1 @@
1
+ # Profile Loss Lieblein