turbo-design 1.3.7__py3-none-any.whl → 1.3.9__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.

Potentially problematic release.


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

Files changed (49) hide show
  1. {turbo_design-1.3.7.dist-info → turbo_design-1.3.9.dist-info}/METADATA +2 -1
  2. turbo_design-1.3.9.dist-info/RECORD +46 -0
  3. {turbo_design-1.3.7.dist-info → turbo_design-1.3.9.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 +237 -155
  8. turbodesign/compressor_math.py +374 -0
  9. turbodesign/compressor_spool.py +837 -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 +159 -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/radeq.py +3 -2
  40. turbodesign/row_factory.py +129 -0
  41. turbodesign/solve_radeq.py +9 -10
  42. turbodesign/{td_math.py → turbine_math.py} +125 -175
  43. turbodesign/turbine_spool.py +984 -0
  44. turbo_design-1.3.7.dist-info/RECORD +0 -33
  45. turbodesign/compressorspool.py +0 -60
  46. turbodesign/loss/turbine/fixedpressureloss.py +0 -25
  47. turbodesign/rotor.py +0 -38
  48. turbodesign/spool.py +0 -317
  49. turbodesign/turbinespool.py +0 -543
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: turbo-design
3
- Version: 1.3.7
3
+ Version: 1.3.9
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
@@ -20,3 +20,4 @@ Requires-Dist: pandas
20
20
  Requires-Dist: plot3d
21
21
  Requires-Dist: pyturbo-aero
22
22
  Requires-Dist: scipy
23
+ Requires-Dist: shapely
@@ -0,0 +1,46 @@
1
+ turbodesign/__init__.py,sha256=VcHpQZK84kYvhgStDWg559mq78Npr9aw2qIH1YzwXls,2798
2
+ turbodesign/agf.py,sha256=3JIPPJ-Jlsq-SPFwbHBaKh5Ul40qKlFQB5EHkPhymTE,11745
3
+ turbodesign/arrayfuncs.py,sha256=wU_dMJfbk3U_X4p9gUXrtlIIYkIDInQE0DmM3B94ykc,1587
4
+ turbodesign/bladerow.py,sha256=NEv9VQchC8cyDCoqwbFjR1PlEdi5JdHiatsbjCkPSaQ,29243
5
+ turbodesign/cantera_gas/co2.yaml,sha256=M2o_RzxV9B9rDkgkXJC-l3voKraFZguTZuKKt4F7S_c,887
6
+ turbodesign/compressor_math.py,sha256=FT0_Hha98op9HmgzenbrcbCeDmAZ14OC2tZV6HezyJE,17557
7
+ turbodesign/compressor_spool.py,sha256=7Z0SP70wDhtfX5YCCyb99IQjpQf400tByk6ATQ5yOO4,35817
8
+ turbodesign/coolant.py,sha256=z5ypCSVw4OaQq41a62cgZM4O7z6_JG8GepLPU3o38iA,596
9
+ turbodesign/deviation/__init__.py,sha256=RBKMk1rtC2glLpZBaGWLnD8fI6Vlme21eCD_qjvAgP0,208
10
+ turbodesign/deviation/axial_compressor.py,sha256=hGPF0-qej7qMOW-VPhqsO46BwASO_3i4CD5Omp1_Lzk,77
11
+ turbodesign/deviation/carter_deviation.py,sha256=SduHA5NHXbZmJSC_dSgiRdjzuErRsA7bT5Axkda9ARE,3339
12
+ turbodesign/deviation/deviation_base.py,sha256=UMgIS7KRnd26R6HPpVpGuGQYTUCVj06_gRitGTMgs30,699
13
+ turbodesign/deviation/fixed_deviation.py,sha256=JsKV40Xz4Uno4-it1a30wXJa3iyt0Wkj8mBnNA7AMIE,1630
14
+ turbodesign/enums.py,sha256=blP0A5xS1yi54Pqvy9ujMdzTSWHou13nQc_STqqZSos,1019
15
+ turbodesign/flow_math.py,sha256=HHqox2-9mFb8MqSC0n2o3Q5GpC81zmYsMJQOX8BJ43E,7200
16
+ turbodesign/inlet.py,sha256=HIJCE3c3y8G-ESvIqX7m7kiqEae688MNesOZGzYzaAM,11602
17
+ turbodesign/isentropic.py,sha256=PcC8G463-zWzHcUmwgebZp-NDS_mxivufnvrQILqra8,3889
18
+ turbodesign/loss/__init__.py,sha256=eABmmypEC-Nny0ctAiH-av_7E4-JK8aeUycFURgaJEE,150
19
+ turbodesign/loss/compressor/OTAC_README.md,sha256=4pLkRobJh22A9Ds5X-bHYF7xVR0eNQl8VUQsAvWg_YU,2032
20
+ turbodesign/loss/compressor/__init__.py,sha256=sslS1fkvCDDEAQqy7cnwITy5DHqh35Utx_YfoG20yXo,1493
21
+ turbodesign/loss/compressor/diffusion.py,sha256=Mh0B69TYm4mDfDBpsDkWuHxpgwaqRddx22GgFKVNbd8,2177
22
+ turbodesign/loss/compressor/lieblein.py,sha256=AVdQPyjVhKubX_XiOM-j6KHeYmJ5zs1zb30R6M0kWZQ,24
23
+ turbodesign/loss/compressor/otac.py,sha256=6xB15-3y8cNRVSc2XsZi00abU3zIxiVCTSLzvJG6eVc,31232
24
+ turbodesign/loss/compressor/references/schobeiri-2012-shock-loss-model-for-transonic-and-supersonic-axial-compressors-with-curved-blades.pdf,sha256=VwTYf6i1DskgEnKk2eCDluSQSixq2qVMkiH1RxCssCY,409616
25
+ turbodesign/loss/fixedpolytropic.py,sha256=SdekAXL_F6HQ6ZSbN7lxNVAhu5A8Fb17JLSalWeDIAI,947
26
+ turbodesign/loss/fixedpressureloss.py,sha256=WE0NqfqOD_myn_mo7qTNEp8MHHyLS_XbDQ2lo-Wy89w,1118
27
+ turbodesign/loss/losstype.py,sha256=e_AGwsUFroTChVJirOJlte6Ymi-JVSi81S06OfR3IOA,860
28
+ turbodesign/loss/turbine/TD2.py,sha256=brujJXTYd8D2lTa622IiI_MJx7x6OQ0i62Df5jWtHRg,4846
29
+ turbodesign/loss/turbine/__init__.py,sha256=re58JgTpvkkviCICa0QNbHD6YTHEthrp9JsSkOmw4No,236
30
+ turbodesign/loss/turbine/ainleymathieson.py,sha256=nQjvFyu2MSQwltHzqPwmFsrG6D7aoYe6nSs5CYHwD-k,6013
31
+ turbodesign/loss/turbine/craigcox.py,sha256=IaF1_qh0CEoKz5VfppwfIBdX3AVeryWgfnqkVeeYmD4,10095
32
+ turbodesign/loss/turbine/fixedefficiency.py,sha256=a0sJvJrINHrwJb5cBZGOHIbG3MVasifuRvsHB00SLfs,924
33
+ turbodesign/loss/turbine/kackerokapuu.py,sha256=Gk_coaARFAxpvhr0vEHOwn1qtJ3jT06L-p6yS_B5Ys8,7388
34
+ turbodesign/loss/turbine/traupel.py,sha256=M6t_Yme10eAA0bKfJnRj_xmrlwkM5tK1hG86_uthzGA,4487
35
+ turbodesign/lossinterp.py,sha256=B2KEobp-nD9jwD6UINgBmTlH9kKyWg3UNvXxqfUsr-k,6198
36
+ turbodesign/outlet.py,sha256=YigceKWqWzcyl6S4OacYJ6a-83tIDbZikHKqLvksrk0,4884
37
+ turbodesign/passage.py,sha256=PBHjaGngprG3cNFEiWPFQ7Sw1lPZ0x9AdjRV52ilCZM,14491
38
+ turbodesign/radeq.py,sha256=LEqYxZZocCZyVPhY09MNFTnAkEXM740-SR6QxXW8Ssg,6522
39
+ turbodesign/row_factory.py,sha256=3q97xK6edrS8GERXw_Y6vrvpRqiNOZARnw4JThzegBs,4892
40
+ turbodesign/solve_radeq.py,sha256=NqPjeVnWtI9ULd7f8wtm_g9QhCz8ZuAiXruxsJpmdGM,1948
41
+ turbodesign/stage.py,sha256=UP45sDKDLsAkO_WfDWJ6kqXU7cYKh_4QO01QZnSN1oQ,166
42
+ turbodesign/turbine_math.py,sha256=oNOyL5ecp1PLLnRuZFZI7fbN0rPAJGSQniaDKDslEkc,15935
43
+ turbodesign/turbine_spool.py,sha256=zG6NQTTm0syFlLuuaQSSWUAEgH7SQ1vI6T8JXtuwx-k,43692
44
+ turbo_design-1.3.9.dist-info/METADATA,sha256=ldJWFMHorhV-P1PyUaxjyaVR3UK1RJJljPx2exT8TZA,808
45
+ turbo_design-1.3.9.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
46
+ turbo_design-1.3.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
turbodesign/__init__.py CHANGED
@@ -1,9 +1,62 @@
1
- from .turbinespool import TurbineSpool
2
- from .compressorspool import CompressorSpool
1
+ from .turbine_spool import TurbineSpool
3
2
  from .stage import Stage
4
- from .enums import LossType, RowType, PassageType, MassflowConstraint
3
+ from .enums import LossType, RowType, PassageType
5
4
  from .inlet import Inlet
6
5
  from .bladerow import BladeRow
6
+ from .coolant import Coolant
7
7
  from .lossinterp import LossInterp
8
8
  from .passage import Passage
9
- from .outlet import Outlet
9
+ from .outlet import Outlet
10
+ from .deviation import DeviationBaseClass, FixedDeviation
11
+ from .row_factory import make_blade_row, make_rotor_row, make_stator_row
12
+ from .agf import AGF_Setup, Inlet_bcs, Outlet_bcs, Settings, Clearance, Domain, read_agf, plot_airfoil_inputs, plot_airfoil_inputs_2D
13
+
14
+ # turbodesign/__init__.py
15
+ from importlib import import_module
16
+
17
+ __all__ = [
18
+ "TurbineSpool", "Stage", "Inlet", "Outlet", "BladeRow", "Coolant",
19
+ "Passage", "RowType", "PassageType", "LossType",
20
+ "LossInterp", "DeviationBaseClass", "FixedDeviation",
21
+ "make_blade_row", "make_rotor_row", "make_stator_row",
22
+ "FixedPolytropicEfficiency",
23
+ "AGF_Setup", "Inlet_bcs", "Outlet_bcs", "Settings", "Clearance", "Domain", "read_agf",
24
+ "plot_airfoil_inputs", "plot_airfoil_inputs_2D",
25
+ ]
26
+
27
+ _module_map = {
28
+ "TurbineSpool": ("turbodesign.turbine_spool", "TurbineSpool"),
29
+ "Stage": ("turbodesign.stage", "Stage"),
30
+ "Inlet": ("turbodesign.inlet", "Inlet"),
31
+ "Outlet": ("turbodesign.outlet", "Outlet"),
32
+ "BladeRow": ("turbodesign.bladerow", "BladeRow"),
33
+ "Coolant": ("turbodesign.coolant", "Coolant"),
34
+ "Passage": ("turbodesign.passage", "Passage"),
35
+ "LossType": ("turbodesign.enums", "LossType"),
36
+ "RowType": ("turbodesign.enums", "RowType"),
37
+ "PassageType": ("turbodesign.enums", "PassageType"),
38
+ "LossInterp": ("turbodesign.lossinterp", "LossInterp"),
39
+ "FixedPolytropicEfficiency": ("turbodesign.loss.fixedpolytropic", "FixedPolytropicEfficiency"),
40
+ "DeviationBaseClass": ("turbodesign.deviation", "DeviationBaseClass"),
41
+ "FixedDeviation": ("turbodesign.deviation", "FixedDeviation"),
42
+ "make_blade_row": ("turbodesign.row_factory", "make_blade_row"),
43
+ "make_rotor_row": ("turbodesign.row_factory", "make_rotor_row"),
44
+ "make_stator_row": ("turbodesign.row_factory", "make_stator_row"),
45
+ "AGF_Setup": ("turbodesign.agf", "AGF_Setup"),
46
+ "Inlet_bcs": ("turbodesign.agf", "Inlet_bcs"),
47
+ "Outlet_bcs": ("turbodesign.agf", "Outlet_bcs"),
48
+ "Settings": ("turbodesign.agf", "Settings"),
49
+ "Clearance": ("turbodesign.agf", "Clearance"),
50
+ "Domain": ("turbodesign.agf", "Domain"),
51
+ "read_agf": ("turbodesign.agf", "read_agf"),
52
+ "plot_airfoil_inputs": ("turbodesign.agf", "plot_airfoil_inputs"),
53
+ "plot_airfoil_inputs_2D": ("turbodesign.agf", "plot_airfoil_inputs_2D"),
54
+ }
55
+
56
+ def __getattr__(name: str):
57
+ try:
58
+ mod_name, attr = _module_map[name]
59
+ except KeyError:
60
+ raise AttributeError(name)
61
+ mod = import_module(mod_name)
62
+ return getattr(mod, attr)
turbodesign/agf.py ADDED
@@ -0,0 +1,346 @@
1
+ """AGF generation and parsing utilities."""
2
+
3
+ from dataclasses import dataclass, asdict
4
+ from typing import List
5
+
6
+ import numpy as np
7
+ import numpy.typing as npt
8
+ import matplotlib.pyplot as plt
9
+
10
+
11
+ @dataclass
12
+ class Inlet_bcs:
13
+ ptin: float
14
+ ttin: float
15
+ pspan: float
16
+ machin: float
17
+ alpin: float
18
+ phiin: float
19
+
20
+
21
+ @dataclass
22
+ class Outlet_bcs:
23
+ rpm: float
24
+ gamma: float
25
+ psout: float
26
+ twall: float
27
+ molwt: float
28
+
29
+
30
+ @dataclass
31
+ class Domain:
32
+ xhup: float # xhub upstream
33
+ rhup: float # rhub upstream
34
+ xtup: float # xtip upstream
35
+ rtup: float # rtip upstream
36
+
37
+ xhdw: float # xhub downwind
38
+ rhdw: float # rhub downwind
39
+ xtdw: float # xtip downwind
40
+ rtdw: float # rtip downwind
41
+
42
+
43
+ @dataclass
44
+ class Settings:
45
+ nprof: int = 1 # number of spanwise points that define pitched average inlet profile
46
+ ifang: int = 10 # 0 - adiabatic wall, 1 - temperature wall
47
+ hbl: float = 1 # Inlet hub boundary layer thickness as percent span
48
+ tbl: float = 1 # Inlet tip boundary layer thickness as percent span
49
+
50
+ nblades: int = 8 # number of blades
51
+ npts: int = 300 # number of points per blade
52
+ nspans: int = 3 # number of spans/sections
53
+ ity: int = 5 # What format are the blades in.
54
+ # ITY 5 = x rth r
55
+ # ITY 7 = x1,theta1,r1,x2,theta2,r2
56
+ # ITY 10 = x1,y1,z1,x2,y2,z2
57
+ iym: int = 0 # 0 = do not flip airfoil along x-axis
58
+ tcls: int = 1 # 1 = has tip clearance
59
+ hcls: int = 0 # 0 = no hub clearance
60
+ lete: int = 10 # do not modify leading edge or trailing edge
61
+ isplit: int = 0 # no splitters
62
+
63
+ nht: int = 1 # Number of axial points defining the endwall
64
+
65
+
66
+ @dataclass
67
+ class Clearance:
68
+ tlecl: float # tip leading edge clearance
69
+ tmccl: float # tip mid clearance
70
+ ttecl: float # tip te clearance
71
+ hlecl: float = 0 # hub le clearance
72
+ hmccl: float = 0 # hub mid clearance
73
+ htecl: float = 0 # hub te clearance
74
+
75
+
76
+ class AGF_Setup:
77
+ def __init__(self, template_file: str = "template.agf", name="radial-turbine"):
78
+ self.agf_template = template_file
79
+ self.name = name
80
+ self.endwall: str = ""
81
+ self.sections: str = ""
82
+ self.clearance: Clearance
83
+ self.settings: Settings
84
+ self.domain: Domain
85
+ self.inlet: Inlet_bcs
86
+ self.outlet: Outlet_bcs
87
+
88
+ def add_passage(self, hub: npt.NDArray, shroud: npt.NDArray):
89
+ """Add passage hub/shroud geometry."""
90
+ domain = Domain(
91
+ xhup=hub[0, 0],
92
+ rhup=hub[0, 1],
93
+ xtup=shroud[0, 0],
94
+ rtup=shroud[0, 1],
95
+ xhdw=hub[-1, 0],
96
+ rhdw=hub[-1, 1],
97
+ xtdw=shroud[-1, 0],
98
+ rtdw=shroud[-1, 1],
99
+ )
100
+ endwall = []
101
+ for i in range(hub.shape[0]):
102
+ xl = hub[i, 0]
103
+ rl = hub[i, 1]
104
+ xu = shroud[i, 0]
105
+ ru = shroud[i, 1]
106
+ if i < hub.shape[0] - 1:
107
+ endwall.append(f"{xl:.4f} {rl:.4f} {xu:.4f} {ru:.4f}\n")
108
+ else:
109
+ endwall.append(f"{xl:.4f} {rl:.4f} {xu:.4f} {ru:.4f}")
110
+ self.endwall = "".join(endwall)
111
+ self.domain = domain
112
+ self.settings.nht = hub.shape[0]
113
+
114
+ def add_blade(self, ss: npt.NDArray, ps: npt.NDArray, IsDuct: bool = False):
115
+ """Add the blade geometry."""
116
+ sections = []
117
+ if IsDuct:
118
+ self.settings.nspans = 0
119
+ else:
120
+ self.settings.nspans = ss.shape[0]
121
+ section_indx = 1
122
+
123
+ for i in range(ss.shape[0]):
124
+ x = np.hstack([ss[i, :, 0], ps[i, 1:-1, 0]])
125
+ y = np.hstack([ss[i, :, 1], ps[i, 1:-1, 1]])
126
+ z = np.hstack([ss[i, :, 2], ps[i, 1:-1, 2]])
127
+ n = len(x) # number of points
128
+ self.settings.npts = n
129
+ r = np.sqrt(y**2 + z**2)
130
+ th = np.arctan2(y, z)
131
+ rth = r * th
132
+ sections.append(f"*SECTION\t{section_indx}\n")
133
+ sections.append(f"- SECTION - {section_indx}\t{n}\n")
134
+ sections.append(">----RAD------XOFF------YOFF------ROTD----CONEANGLE----\n")
135
+ sections.append("0.0000\t0.0000\t0.0000\t0.0000\t0.0000\n")
136
+ sections.append("x rth r\n")
137
+ for j in range(len(x)):
138
+ sections.append(f"{x[j]:0.6f} {rth[j]:0.6f} {r[j]:0.6f}\n")
139
+ section_indx += 1
140
+
141
+ self.sections = "".join(sections)
142
+
143
+ def add_clearance(self, clearance: Clearance):
144
+ self.clearance = clearance
145
+
146
+ def add_settings(self, settings: Settings):
147
+ self.settings = settings
148
+
149
+ def add_inlet(self, inlet: Inlet_bcs):
150
+ self.inlet = inlet
151
+
152
+ def add_outlet(self, outlet: Outlet_bcs):
153
+ self.outlet = outlet
154
+
155
+ def build(self, output_filename: str = "stator.agf"):
156
+ with open(self.agf_template, "r") as file:
157
+ file_content = file.read()
158
+ file_content = file_content.replace("[name]", f"{self.name}")
159
+
160
+ domain_dict = asdict(self.domain)
161
+ clearance_dict = asdict(self.clearance)
162
+ settings_dict = asdict(self.settings)
163
+ inlet_dict = asdict(self.inlet)
164
+ outlet_dict = asdict(self.outlet)
165
+
166
+ with open(output_filename, "w") as f:
167
+ for k, v in domain_dict.items():
168
+ file_content = file_content.replace(f"[{k}]", f"{v:0.4f}")
169
+
170
+ for k, v in clearance_dict.items():
171
+ file_content = file_content.replace(f"[{k}]", f"{v}")
172
+
173
+ for k, v in settings_dict.items():
174
+ file_content = file_content.replace(f"[{k}]", f"{v}")
175
+
176
+ for k, v in inlet_dict.items():
177
+ file_content = file_content.replace(f"[{k}]", f"{v:0.4f}")
178
+
179
+ for k, v in outlet_dict.items():
180
+ file_content = file_content.replace(f"[{k}]", f"{v:0.4f}")
181
+
182
+ if self.sections:
183
+ file_content = file_content.replace("[sections]", self.sections)
184
+
185
+ file_content = file_content.replace("[endwall]", self.endwall)
186
+ f.write(file_content)
187
+
188
+
189
+ def read_agf(file_path: str) -> dict:
190
+ """
191
+ Parse an AGF file and reconstruct the data classes used to build it.
192
+
193
+ Returns:
194
+ dict with keys:
195
+ settings (Settings)
196
+ clearance (Clearance)
197
+ domain (Domain)
198
+ inlet (Inlet_bcs)
199
+ outlet (Outlet_bcs)
200
+ hub (np.ndarray): shape (nht, 2) of x, r (hub)
201
+ shroud (np.ndarray): shape (nht, 2) of x, r (shroud)
202
+ sections (np.ndarray): shape (n_sections, npts, 3) of x, rth, r
203
+ """
204
+ with open(file_path, "r") as f:
205
+ lines = [ln.strip() for ln in f.readlines()]
206
+
207
+ def find_after(marker: str) -> List[str]:
208
+ for idx, ln in enumerate(lines):
209
+ if ln.startswith(marker):
210
+ return lines[idx + 1].split()
211
+ return []
212
+
213
+ # Settings and counts
214
+ nb_vals = find_after("*NBLADE")
215
+ nblades, npts, nspans, ity, iym, tcls, hcls, lete, isplit = [int(float(v)) for v in nb_vals]
216
+
217
+ units_vals = find_after("*UNITS")
218
+ _, nprof, ifang, hbl, tbl, tfree, lfree = units_vals
219
+ nprof = int(nprof)
220
+ ifang = int(ifang)
221
+
222
+ settings = Settings(
223
+ nprof=nprof,
224
+ ifang=ifang,
225
+ hbl=float(hbl),
226
+ tbl=float(tbl),
227
+ nblades=nblades,
228
+ npts=npts,
229
+ nspans=nspans,
230
+ ity=int(ity),
231
+ iym=int(iym),
232
+ tcls=int(tcls),
233
+ hcls=int(hcls),
234
+ lete=int(lete),
235
+ isplit=int(isplit),
236
+ )
237
+
238
+ rpm_vals = find_after("*RPMS")
239
+ rpm, gamma, psout, twall, molwt = [float(v) for v in rpm_vals]
240
+ outlet = Outlet_bcs(rpm=rpm, gamma=gamma, psout=psout, twall=twall, molwt=molwt)
241
+
242
+ pspan_vals = find_after("*PSPAN")
243
+ pspan, machin, ptin, ttin, alpin, phiin = [float(v) for v in pspan_vals]
244
+ inlet = Inlet_bcs(ptin=ptin, ttin=ttin, pspan=pspan, machin=machin, alpin=alpin, phiin=phiin)
245
+
246
+ xhup_vals = find_after("*XHUP")
247
+ xhup, rhup, xtup, rtup = [float(v) for v in xhup_vals]
248
+ xhdw_vals = find_after("*XHDW")
249
+ xhdw, rhdw, xtdw, rtdw = [float(v) for v in xhdw_vals]
250
+ domain = Domain(xhup=xhup, rhup=rhup, xtup=xtup, rtup=rtup, xhdw=xhdw, rhdw=rhdw, xtdw=xtdw, rtdw=rtdw)
251
+
252
+ tlecl_vals = find_after("*TLECL")
253
+ hlecl_vals = find_after("*HLECL")
254
+ clearance = Clearance(
255
+ tlecl=float(tlecl_vals[0]) if tlecl_vals else 0.0,
256
+ tmccl=float(tlecl_vals[1]) if len(tlecl_vals) > 1 else 0.0,
257
+ ttecl=float(tlecl_vals[2]) if len(tlecl_vals) > 2 else 0.0,
258
+ hlecl=float(hlecl_vals[0]) if hlecl_vals else 0.0,
259
+ hmccl=float(hlecl_vals[1]) if len(hlecl_vals) > 1 else 0.0,
260
+ htecl=float(hlecl_vals[2]) if len(hlecl_vals) > 2 else 0.0,
261
+ )
262
+
263
+ nht_vals = find_after("*NHT")
264
+ if nht_vals:
265
+ settings.nht = int(float(nht_vals[0]))
266
+
267
+ # Endwall parsing
268
+ hub_pts: List[List[float]] = []
269
+ shroud_pts: List[List[float]] = []
270
+ if "*ENDWALL" in lines:
271
+ start = lines.index("*ENDWALL") + 2 # skip header and column line
272
+ idx = start
273
+ while idx < len(lines) and not lines[idx].startswith("*SECTION"):
274
+ parts = [p for p in lines[idx].split() if p]
275
+ if len(parts) == 4:
276
+ xl, rl, xu, ru = [float(p) for p in parts]
277
+ hub_pts.append([xl, rl])
278
+ shroud_pts.append([xu, ru])
279
+ idx += 1
280
+ hub_arr = np.array(hub_pts) if hub_pts else np.zeros((0, 2))
281
+ shroud_arr = np.array(shroud_pts) if shroud_pts else np.zeros((0, 2))
282
+
283
+ # Sections parsing
284
+ sections: List[np.ndarray] = []
285
+ idx = 0
286
+ while idx < len(lines):
287
+ line = lines[idx]
288
+ if line.startswith("*SECTION"):
289
+ idx += 1 # - SECTION - line
290
+ section_info = lines[idx].split()
291
+ npts_section = int(section_info[-1])
292
+ idx += 3 # skip offset/cone headers to column header
293
+ idx += 1 # column header line
294
+ pts = []
295
+ for _ in range(npts_section):
296
+ parts = lines[idx].split()
297
+ if len(parts) >= 3:
298
+ pts.append([float(parts[0]), float(parts[1]), float(parts[2])])
299
+ idx += 1
300
+ sections.append(np.array(pts))
301
+ continue
302
+ idx += 1
303
+
304
+ sections_arr = np.array(sections) if sections else np.zeros((0, 0, 3))
305
+
306
+ return {
307
+ "settings": settings,
308
+ "clearance": clearance,
309
+ "domain": domain,
310
+ "inlet": inlet,
311
+ "outlet": outlet,
312
+ "hub": hub_arr,
313
+ "shroud": shroud_arr,
314
+ "sections": sections_arr,
315
+ }
316
+
317
+
318
+ def plot_airfoil_inputs(nsections: int, npts: int):
319
+ xthr = np.zeros(shape=(nsections, npts, 3)) # section_num x theta r
320
+ with open("AIRFOIL.INPUTS", "r") as f:
321
+ [f.readline() for _ in range(4)] # skip first 4 lines
322
+ for i in range(nsections):
323
+ for j in range(npts):
324
+ line = f.readline()
325
+ temp = [float(p) for p in line.split(" ") if p]
326
+ xthr[i, j, 0] = temp[0]
327
+
328
+
329
+ def plot_airfoil_inputs_2D(nsections: int, npts: int):
330
+ xthr = np.zeros(shape=(nsections, npts, 3))
331
+ with open("AIRFOIL.INPUTS", "r") as f:
332
+ [f.readline() for _ in range(4)]
333
+ for i in range(nsections):
334
+ for j in range(npts):
335
+ line = f.readline()
336
+ temp = [float(p) for p in line.split(" ") if p]
337
+ xthr[i, j, 0] = temp[0]
338
+ xthr[i, j, 1] = temp[1]
339
+ xthr[i, j, 2] = temp[2]
340
+ [f.readline() for _ in range(2)]
341
+ for i in range(nsections):
342
+ plt.plot(xthr[i, :, 0], xthr[i, :, 1], "r", label=f"section {i}")
343
+ plt.axis("equal")
344
+ plt.xlabel("x-axial")
345
+ plt.ylabel("y")
346
+ plt.show()
turbodesign/arrayfuncs.py CHANGED
@@ -12,8 +12,38 @@ def convert_to_ndarray(t) -> np.ndarray:
12
12
  Returns:
13
13
  np.ndarray: variable as an array
14
14
  """
15
+ if hasattr(t, "default_factory"):
16
+ try:
17
+ t = t.default_factory()
18
+ except Exception:
19
+ t = np.array([0.0])
15
20
  if type(t) is not np.ndarray and type(t) is not list: # Scalar
16
21
  t = np.array([t],dtype=float)
17
22
  elif (type(t) is list):
18
23
  t = np.array(t,dtype=float)
19
- return t
24
+ return t
25
+
26
+
27
+ def safe_interpolate(values, src_r, dst_r, default: float = 0.0, radians: bool = False, interp_func=None):
28
+ """Safely convert, default, and interpolate quantities onto target radii."""
29
+ from .bladerow import interpolate_quantities # local import to avoid cycles
30
+
31
+ arr = convert_to_ndarray(values)
32
+ if hasattr(arr, "default_factory"):
33
+ arr = arr.default_factory()
34
+ if arr.size == 0:
35
+ arr = np.array([default], dtype=float)
36
+ src = convert_to_ndarray(src_r)
37
+ dst = convert_to_ndarray(dst_r)
38
+ if src.size == 0:
39
+ src = np.linspace(0, 1, len(arr))
40
+ if arr.size > 1 and src.size != arr.size:
41
+ src = np.linspace(0, 1, len(arr))
42
+ if arr.size == 1:
43
+ arr = arr[0] * np.ones_like(dst)
44
+ else:
45
+ f = interp_func if interp_func is not None else interpolate_quantities
46
+ arr = f(arr, src, dst)
47
+ if radians:
48
+ arr = np.radians(arr)
49
+ return arr