Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/pysagas/cfd/deck.py: 42%
107 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-30 04:27 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-30 04:27 +0000
1import copy
2import pandas as pd
3from typing import Optional, Union
4from abc import ABC, abstractmethod
5from pysagas.cfd.solver import FlowResults, SensitivityResults
8class AbstractDeck(ABC):
9 @abstractmethod
10 def __init__(self) -> None:
11 pass
13 @abstractmethod
14 def __repr__(self) -> None:
15 pass
17 @abstractmethod
18 def __str__(self) -> None:
19 pass
21 @abstractmethod
22 def insert(self):
23 """Insert data into the deck."""
24 pass
26 @abstractmethod
27 def to_csv(self):
28 """Write the deck to csv."""
29 pass
31 @abstractmethod
32 def from_csv(self, **kwargs):
33 """Load the deck from csv."""
34 pass
36 # @abstractmethod
37 # def interpolate(self, **kwargs):
38 # # TODO - implement with scipy.interpolate.interpn
39 # pass
42class Deck(AbstractDeck):
43 TYPE = "deck"
45 def __init__(
46 self,
47 inputs: list[str],
48 columns: list[str],
49 **kwargs,
50 ) -> None:
51 """Instantiate a new deck.
53 Parameters
54 ----------
55 inputs : list[str]
56 A list of the inputs to this aerodeck. For example, ["aoa", "mach"].
58 columns : list[str]
59 A list of column names to use for this deck. For example, ["CL", "CD", "Cm"].
60 """
61 self._deck = pd.DataFrame(columns=inputs + columns)
62 self._inputs = inputs
64 def __repr__(self) -> None:
65 return f"{self.TYPE}"
67 def __str__(self) -> None:
68 return self.__repr__()
70 @property
71 def deck(self) -> pd.DataFrame:
72 return self._deck.drop_duplicates()
74 def to_csv(self, file_prefix: Optional[str] = None):
75 """Save the deck to CSV file.
77 Parameters
78 ----------
79 file_prefix : str, optional
80 The CSV file name prefix. If None is provided, the deck __repr__
81 will be used. The default is None.
82 """
83 file_prefix = file_prefix if file_prefix else self.__repr__()
84 self.deck.to_csv(f"{file_prefix}.csv", index=False)
87class MultiDeck(AbstractDeck):
88 """Collection of multiple Deck objects."""
90 def __init__(
91 self,
92 inputs: list[str],
93 parameters: list[str],
94 base_deck: Deck,
95 **kwargs,
96 ) -> None:
97 """Instantiate a new deck collection.
99 Parameters
100 ----------
101 inputs : list[str]
102 A list of the inputs to this deck. For example, ["aoa", "mach"].
104 parameters : list[str]
105 A list of the parameters to this deck. For example, ["wingspan", "length"].
107 base_deck : Deck
108 The base deck to initalise self._decks with.
109 """
110 self._inputs = inputs
111 self._parameters = list(parameters)
112 self._decks: dict[str, Deck] = {p: copy.deepcopy(base_deck) for p in parameters}
115class AeroDeck(Deck):
116 """Aerodynamic coefficient deck."""
118 TYPE = "aerodeck"
120 def __init__(
121 self,
122 inputs: list[str],
123 columns: list[str] = ["CL", "CD", "Cm"],
124 a_ref: float = 1,
125 c_ref: float = 1,
126 ) -> None:
127 """Instantiate a new aerodeck.
129 Parameters
130 ----------
131 inputs : list[str]
132 A list of the inputs to this aerodeck. For example, ["aoa", "mach"].
134 columns : list[str], optional
135 A list of column names to use for this deck. The default is ["CL", "CD", "Cm"].
137 a_ref : float, optional
138 The reference area. The default is 1.
140 c_ref : float, optional
141 The reference length. The default is 1.
142 """
144 # Save reference properties
145 self._a_ref = a_ref
146 self._c_ref = c_ref
147 self._columns = columns
149 # Complete instantiation
150 super().__init__(inputs, columns)
152 def insert(self, result: Union[FlowResults, dict[str, float]], **kwargs):
153 """Insert aerodynamic coefficients into the aerodeck.
155 Parameters
156 ----------
157 result : FlowResults | dict
158 The aerodynamic coefficients to be inserted, either as a PySAGAS native
159 FlowResults object, or a dictionary with keys matching the data columns
160 specified on instantiation of the deck.
162 See Also
163 --------
164 FlowResults
165 """
166 # Check inputs
167 inputs_given = [i in kwargs for i in self._inputs]
168 if not all(inputs_given):
169 raise Exception(
170 "Please provide all input values when inserting new result."
171 )
173 # Process results
174 if isinstance(result, FlowResults):
175 # Get coefficients
176 coefficients = result.coefficients(A_ref=self._a_ref, c_ref=self._c_ref)
178 # Extract data
179 # TODO - use columns provided in init
180 data = {
181 "CL": coefficients[0],
182 "CD": coefficients[1],
183 "Cm": coefficients[2],
184 }
186 else:
187 # Check keys
188 data_given = [i in result for i in self._columns]
189 if not all(data_given):
190 raise Exception(
191 "Please provide a data point for all values when inserting new result."
192 )
194 # Data provided directly
195 data = result
197 # Add inputs
198 data.update(kwargs)
200 # Add to deck
201 self._deck = pd.concat(
202 [self._deck, pd.DataFrame(data, index=[len(self._deck)])]
203 )
205 @classmethod
206 def from_csv(
207 cls, filepath: str, inputs: list[str], a_ref: float = 1, c_ref: float = 1
208 ):
209 """Create an Aerodeck from a CSV file.
211 Parameters
212 ----------
213 filepath : str
214 The filepath to the csv file containing aerodeck data.
216 inputs : list[str]
217 A list of the inputs to this aerodeck. For example, ["aoa", "mach"].
219 a_ref : float, optional
220 The reference area. The default is 1.
222 c_ref : float, optional
223 The reference length. The default is 1.
224 """
226 # Read data from file
227 data = pd.read_csv(filepath)
229 # Extract inputs and columns
230 columns = list(data.columns)
231 inputs = [columns.pop(columns.index(coef)) for coef in inputs]
233 # Instantiate aerodeck
234 aerodeck = cls(inputs=inputs, columns=columns, a_ref=a_ref, c_ref=c_ref)
235 aerodeck._deck = data
236 return aerodeck
239class SensDeck(MultiDeck):
240 TYPE = "sensdeck"
242 def __init__(
243 self,
244 inputs: list[str],
245 parameters: list[str],
246 a_ref: float = 1,
247 c_ref: float = 1,
248 **kwargs,
249 ) -> None:
250 """Instantiate a new sensitivity deck.
252 This object is a collection of `AeroDeck`s, containing aerodynamic sensitivity
253 information.
255 Parameters
256 ----------
257 inputs : list[str]
258 A list of the inputs to this deck. For example, ["aoa", "mach"].
260 parameters : list[str]
261 A list of the parameters to this deck. For example, ["wingspan", "length"].
263 a_ref : float, optional
264 The reference area. The default is 1.
266 c_ref : float, optional
267 The reference length. The default is 1.
269 See Also
270 --------
271 AeroDeck
272 """
274 # Save reference properties
275 self._a_ref = a_ref
276 self._c_ref = c_ref
278 # Create base sensitivity deck
279 columns = ["dCL", "dCD", "dCm"]
280 base_deck = AeroDeck(inputs, columns, a_ref, c_ref)
282 # Complete instantiation
283 super().__init__(
284 inputs=inputs, parameters=parameters, base_deck=base_deck, **kwargs
285 )
287 def __repr__(self) -> None:
288 return f"{self.TYPE}"
290 def __str__(self) -> None:
291 return self.__repr__()
293 def insert(self, result: SensitivityResults, **kwargs):
294 # Get coefficients
295 f_sens, m_sens = result.coefficients(A_ref=self._a_ref, c_ref=self._c_ref)
297 for param in self._parameters:
298 # Extract data for this parameter
299 data = {
300 "dCL": f_sens.loc[param]["dCL"],
301 "dCD": f_sens.loc[param]["dCD"],
302 "dCm": m_sens.loc[param]["dMz/dp"],
303 }
305 # Insert this data into the respective parameter deck
306 self._decks[param].insert(result=data, **kwargs)
308 @classmethod
309 def from_csv(
310 cls,
311 param_filepaths: dict[str, str],
312 inputs: list[str],
313 a_ref: float = 1,
314 c_ref: float = 1,
315 ):
316 """Create a SensDeck from a collection of CSV files.
318 Parameters
319 ----------
320 param_filepaths : dict[str, str]
321 A dictionary of filepaths, keyed by the associated parameter.
323 inputs : list[str]
324 A list of the inputs to this aerodeck. For example, ["aoa", "mach"].
326 a_ref : float, optional
327 The reference area. The default is 1.
329 c_ref : float, optional
330 The reference length. The default is 1.
331 """
332 decks = {}
333 for param, filepath in param_filepaths.items():
334 # Load data
335 deck = AeroDeck.from_csv(
336 filepath=filepath, inputs=inputs, a_ref=a_ref, c_ref=c_ref
337 )
338 decks[param] = deck
340 # Extract inputs
341 inputs = deck._inputs
343 # Instantiate sensdeck
344 sensdeck = cls(
345 inputs=inputs, parameters=param_filepaths.keys(), a_ref=a_ref, c_ref=c_ref
346 )
348 # Overwrite with loaded decks
349 sensdeck._decks = decks
351 return sensdeck
353 def to_csv(self, file_prefix: Optional[str] = None):
354 """Save the decks to CSV file.
356 Parameters
357 ----------
358 file_prefix : str, optional
359 The CSV file name prefix. If None is provided, the deck __repr__
360 will be used. The default is None.
361 """
362 file_prefix = file_prefix if file_prefix else self.__repr__()
363 for p, deck in self._decks.items():
364 deck.to_csv(file_prefix=f"{p}_{file_prefix}")
366 @property
367 def decks(self) -> dict[str, AeroDeck]:
368 decks = {p: deck.deck for p, deck in self._decks.items()}
369 return decks