def plot_winds_at_tropopause_altitude(): fig, ax = plt.subplots() day_of_years = np.linspace(0, 365, 150) latitudes = np.linspace(-80, 80, 120) Day_of_years, Latitudes = np.meshgrid(day_of_years, latitudes) winds = wind_speed_world_95( altitude=tropopause_altitude(Latitudes.flatten(), Day_of_years.flatten()), latitude=Latitudes.flatten(), day_of_year=Day_of_years.flatten(), ).reshape(Latitudes.shape) args = [day_of_years, latitudes, winds] levels = np.arange(0, 80.1, 5) CS = plt.contour(*args, levels=levels, linewidths=0.5, colors="k", alpha=0.7) CF = plt.contourf(*args, levels=levels, cmap='viridis_r', alpha=0.7, extend="max") cbar = plt.colorbar(label="Wind Speed [m/s]", extendrect=True) ax.clabel(CS, inline=1, fontsize=9, fmt="%.0f m/s") plt.xticks( np.linspace(0, 365, 13)[:-1], ("Jan. 1", "Feb. 1", "Mar. 1", "Apr. 1", "May 1", "June 1", "July 1", "Aug. 1", "Sep. 1", "Oct. 1", "Nov. 1", "Dec. 1"), rotation=40) lat_label_vals = np.arange(-80, 80.1, 20) lat_labels = [] for lat in lat_label_vals: if lat >= 0: lat_labels.append(f"{lat:.0f}N") else: lat_labels.append(f"{-lat:.0f}S") plt.yticks(lat_label_vals, lat_labels) show_plot( f"95th-Percentile Wind Speeds at Tropopause Altitude", xlabel="Day of Year", ylabel="Latitude", )
def interpolated_model(): np.random.seed(0) # Set a seed for repeatability. ### Make some data x1 = np.linspace(0, 10, 11) x2 = np.linspace(0, 10, 21) X1, X2 = np.meshgrid(x1, x2, indexing="ij") return InterpolatedModel( x_data_coordinates={ "x1": x1, "x2": x2, }, y_data_structured=underlying_function_2D(X1, X2), )
def plot_tropopause_altitude(): fig, ax = plt.subplots() day_of_years = np.linspace(0, 365, 250) latitudes = np.linspace(-80, 80, 200) Day_of_years, Latitudes = np.meshgrid(day_of_years, latitudes) trop_alt = tropopause_altitude(Latitudes.flatten(), Day_of_years.flatten()).reshape( Latitudes.shape) args = [day_of_years, latitudes, trop_alt / 1e3] levels = np.arange(10, 20.1, 1) CS = plt.contour(*args, levels=levels, linewidths=0.5, colors="k", alpha=0.7) CF = plt.contourf(*args, levels=levels, cmap='viridis_r', alpha=0.7, extend="both") cbar = plt.colorbar(label="Tropopause Altitude [km]", extendrect=True) ax.clabel(CS, inline=1, fontsize=9, fmt="%.0f km") plt.xticks( np.linspace(0, 365, 13)[:-1], ("Jan. 1", "Feb. 1", "Mar. 1", "Apr. 1", "May 1", "June 1", "July 1", "Aug. 1", "Sep. 1", "Oct. 1", "Nov. 1", "Dec. 1"), rotation=40) lat_label_vals = np.arange(-80, 80.1, 20) lat_labels = [] for lat in lat_label_vals: if lat >= 0: lat_labels.append(f"{lat:.0f}N") else: lat_labels.append(f"{-lat:.0f}S") plt.yticks(lat_label_vals, lat_labels) show_plot( f"Tropopause Altitude by Season and Latitude", xlabel="Day of Year", ylabel="Latitude", )
def spy( matrix, show=True, ): """ Plots the sparsity pattern of a matrix. :param matrix: The matrix to plot the sparsity pattern of. [2D ndarray or CasADi array] :param show: Whether or not to show the sparsity plot. [boolean] :return: The figure to be plotted [go.Figure] """ try: matrix = matrix.toarray() except: pass abs_m = np.abs(matrix) sparsity_pattern = abs_m >= 1e-16 matrix[sparsity_pattern] = np.log10(abs_m[sparsity_pattern] + 1e-16) j_index_map, i_index_map = np.meshgrid(np.arange(matrix.shape[1]), np.arange(matrix.shape[0])) i_index = i_index_map[sparsity_pattern] j_index = j_index_map[sparsity_pattern] val = matrix[sparsity_pattern] val = np.ones_like(i_index) fig = go.Figure( data=go.Heatmap( y=i_index, x=j_index, z=val, # type='heatmap', colorscale='RdBu', showscale=False, ), ) fig.update_layout( plot_bgcolor="black", xaxis=dict(showgrid=False, zeroline=False), yaxis=dict(showgrid=False, zeroline=False, autorange="reversed", scaleanchor="x", scaleratio=1), width=800, height=800 * (matrix.shape[0] / matrix.shape[1]), ) if show: fig.show() return fig
def test_calculate_induced_velocity_panel_coordinates(): X, Y = np.meshgrid( np.linspace(-1, 2, 50), np.linspace(-1, 1, 50), indexing='ij', ) X = X.flatten() Y = Y.flatten() U, V = calculate_induced_velocity_line_singularities( x_field=X, y_field=Y, x_panels=np.array([-0.5, 1.5]), y_panels=np.array([0, 0]), gamma=np.array([1, 1]), sigma=np.array([1, 1]), )
def test_multidimensional_power_law_fitting(): np.random.seed(0) # Set a seed for repeatability. ### Make some data z(x,y) x = np.logspace(0, 3) y = np.logspace(0, 3) X, Y = np.meshgrid(x, y, indexing="ij") noise = np.random.lognormal(mean=0, sigma=0.05) Z = 0.5 * X**0.75 * Y**1.25 * noise ### Fit data def model(x, p): return (p["multiplier"] * x["X"]**p["X_power"] * x["Y"]**p["Y_power"]) x_data = { "X": X.flatten(), "Y": Y.flatten(), } fitted_model = FittedModel( model=model, x_data=x_data, y_data=Z.flatten(), parameter_guesses={ "multiplier": 1, "X_power": 1, "Y_power": 1, }, parameter_bounds={ "multiplier": (None, None), "X_power": (None, None), "Y_power": (None, None), }, put_residuals_in_logspace=True # Putting residuals in logspace minimizes the norm of log-error instead of absolute error ) ### Check that the fit is right assert fitted_model.parameters["multiplier"] == pytest.approx(0.546105, abs=1e-3) assert fitted_model.parameters["X_power"] == pytest.approx(0.750000, abs=1e-3) assert fitted_model.parameters["Y_power"] == pytest.approx(1.250000, abs=1e-3)
def plot_winds_at_day(day_of_year=0): fig, ax = plt.subplots() altitudes = np.linspace(0, 30000, 150) latitudes = np.linspace(-80, 80, 120) Altitudes, Latitudes = np.meshgrid(altitudes, latitudes) winds = wind_speed_world_95( altitude=Altitudes.flatten(), latitude=Latitudes.flatten(), day_of_year=day_of_year * np.ones_like(Altitudes.flatten()), ).reshape(Altitudes.shape) args = [altitudes / 1e3, latitudes, winds] levels = np.arange(0, 80.1, 5) CS = plt.contour(*args, levels=levels, linewidths=0.5, colors="k", alpha=0.7) CF = plt.contourf(*args, levels=levels, cmap='viridis_r', alpha=0.7, extend="max") cbar = plt.colorbar(label="Wind Speed [m/s]", extendrect=True) ax.clabel(CS, inline=1, fontsize=9, fmt="%.0f m/s") lat_label_vals = np.arange(-80, 80.1, 20) lat_labels = [] for lat in lat_label_vals: if lat >= 0: lat_labels.append(f"{lat:.0f}N") else: lat_labels.append(f"{-lat:.0f}S") plt.yticks(lat_label_vals, lat_labels) show_plot( f"95th-Percentile Wind Speeds at Day {day_of_year:.0f}", xlabel="Altitude [km]", ylabel="Latitude", )
def test_interpn_linear_multiple_samples(): ### NumPy test def value_func_3d(x, y, z): return 2 * x + 3 * y - z x = np.linspace(0, 5, 10) y = np.linspace(0, 5, 20) z = np.linspace(0, 5, 30) points = (x, y, z) values = value_func_3d(*np.meshgrid(*points, indexing="ij")) point = np.array([ [2.21, 3.12, 1.15], [3.42, 0.81, 2.43] ]) value = np.interpn( points, values, point ) assert np.all( value == pytest.approx( value_func_3d( *[ point[:, i] for i in range(point.shape[1]) ] ) ) ) assert len(value) == 2 ### CasADi test point = cas.DM(point) value = np.interpn( points, values, point ) value_actual = value_func_3d( *[ np.array(point[:, i]) for i in range(point.shape[1]) ] ) for i in range(len(value)): assert value[i] == pytest.approx(float(value_actual[i])) assert value.shape == (2,)
def test_interpn_bspline_casadi(): """ The bspline method should interpolate seperable cubic multidimensional polynomials exactly. """ def func(x, y, z): # Sphere function return x ** 3 + y ** 3 + z ** 3 x = np.linspace(-5, 5, 10) y = np.linspace(-5, 5, 20) z = np.linspace(-5, 5, 30) points = (x, y, z) values = func( *np.meshgrid(*points, indexing="ij") ) point = np.array([0.4, 0.5, 0.6]) value = np.interpn( points, values, point, method="bspline" ) assert value == pytest.approx(func(*point))
def test_interpn_bounds_error_one_sample(): def value_func_3d(x, y, z): return 2 * x + 3 * y - z x = np.linspace(0, 5, 10) y = np.linspace(0, 5, 20) z = np.linspace(0, 5, 30) points = (x, y, z) values = value_func_3d(*np.meshgrid(*points, indexing="ij")) point = np.array([5.21, 3.12, 1.15]) with pytest.raises(ValueError): value = np.interpn( points, values, point ) ### CasADi test point = cas.DM(point) with pytest.raises(ValueError): value = np.interpn( points, values, point )
def test_interpn_linear(): ### NumPy test def value_func_3d(x, y, z): return 2 * x + 3 * y - z x = np.linspace(0, 5, 10) y = np.linspace(0, 5, 20) z = np.linspace(0, 5, 30) points = (x, y, z) values = value_func_3d(*np.meshgrid(*points, indexing="ij")) point = np.array([2.21, 3.12, 1.15]) value = np.interpn( points, values, point ) assert value == pytest.approx(value_func_3d(*point)) ### CasADi test point = cas.DM(point) value = np.interpn( points, values, point ) assert value == pytest.approx(float(value_func_3d(point[0], point[1], point[2])))
x_left=-1, y_left=-1, z_left=0, x_right=-1, y_right=1, z_right=0, gamma=1, ) print(u, v, w) ##### Plot grid of single vortex args = (-2, 2, 30) x = np.linspace(*args) y = np.linspace(*args) z = np.linspace(*args) X, Y, Z = np.meshgrid(x, y, z) Xf = X.flatten() Yf = Y.flatten() Zf = Z.flatten() left = [0, -1, 0] right = [0, 1, 0] Uf, Vf, Wf = calculate_induced_velocity_horseshoe( x_field=Xf, y_field=Yf, z_field=Zf, x_left=left[0], y_left=left[1], z_left=left[2],
def __init__(self, x_data: Union[np.ndarray, Dict[str, np.ndarray]], y_data: np.ndarray, x_data_resample: Union[int, Dict[str, Union[int, np.ndarray]]] = 10, resampling_interpolator: object = interpolate.RBFInterpolator, resampling_interpolator_kwargs: Dict[str, Any] = None, fill_value=np.NaN, # Default behavior: return NaN for all inputs outside data range. interpolated_model_kwargs: Dict[str, Any] = None, ): """ Creates the interpolator. Note that data must be unstructured (i.e., point cloud) for general N-dimensional interpolation. Note that if data is either 1D or structured, Args: x_data: Values of the dependent variable(s) in the dataset to be fitted. This is a dictionary; syntax is { var_name:var_data}. * If the model is one-dimensional (e.g. f(x1) instead of f(x1, x2, x3...)), you can instead supply x_data as a 1D ndarray. (If you do this, just treat `x` as an array in your model, not a dict.) y_data: Values of the independent variable in the dataset to be fitted. [1D ndarray of length n] x_data_resample: A parameter that guides how the x_data should be resampled onto a structured grid. * If this is an int, we look at each axis of the `x_data` (here, we'll call this `xi`), and we resample onto a linearly-spaced grid between `min(xi)` and `max(xi)` with `x_data_resample` points. * If this is a dict, it must be a dict where the keys are strings matching the keys of (the dictionary) `x_data`. The values can either be ints or 1D np.ndarrays. * If the values are ints, then that axis is linearly spaced between `min(xi)` and `max(xi)` with `x_data_resample` points. * If the values are 1D np.ndarrays, then those 1D np.ndarrays are used as the resampled spacing for the given axis. resampling_interpolator: Indicates the interpolator to use in order to resample the unstructured data onto a structured grid. Should be analogous to scipy.interpolate.RBFInterpolator in __init__ and __call__ syntax. See reference here: * https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.RBFInterpolator.html resampling_interpolator_kwargs: Indicates keyword arguments (keyword-value pairs, as a dictionary) to pass into the resampling interpolator. fill_value: Gives the value that the interpolator should return for points outside of the interpolation domain. The interpolation domain is defined as the hypercube bounded by the coordinates specified in `x_data_resample`. By default, these coordinates are the tightest axis-aligned hypercube that bounds the point cloud data. If fill_value is None, then the interpolator will attempt to extrapolate if the interpolation method allows. interpolated_model_kwargs: Indicates keyword arguments to pass into the (structured) InterpolatedModel. Also a dictionary. See aerosandbox.InterpolatedModel for documentation on possible inputs here. """ if resampling_interpolator_kwargs is None: resampling_interpolator_kwargs = {} if interpolated_model_kwargs is None: interpolated_model_kwargs = {} try: # Try to use the InterpolatedModel initializer. If it doesn't work, then move on. super().__init__( x_data_coordinates=x_data, y_data_structured=y_data, ) return except ValueError: pass # If it didn't work, this implies that x_data is multidimensional, and hence a dict-like object. Validate this. try: # Determine type of `x_data` x_data.keys() x_data.values() x_data.items() except AttributeError: raise TypeError("`x_data` must be a dict-like object!") # Make the interpolator, based on x_data and y_data. if resampling_interpolator == interpolate.RBFInterpolator: resampling_interpolator_kwargs = { "kernel": "thin_plate_spline", "degree": 1, **resampling_interpolator_kwargs } interpolator = resampling_interpolator( y=np.stack(tuple(x_data.values()), axis=1), d=y_data, **resampling_interpolator_kwargs ) # If x_data_resample is an int, make it into a dict that matches x_data. if isinstance(x_data_resample, int): x_data_resample = { k: x_data_resample for k in x_data.keys() } # Now, x_data_resample should be dict-like. Validate this. try: x_data_resample.keys() x_data_resample.values() x_data_resample.items() except AttributeError: raise TypeError("`x_data_resample` must be a dict-like object!") # Go through x_data_resample, and replace any values that are ints with linspaced arrays. for k, v in x_data_resample.items(): if isinstance(v, int): x_data_resample[k] = np.linspace( np.min(x_data[k]), np.max(x_data[k]), v ) x_data_coordinates: Dict = x_data_resample x_data_structured_values = [ xi.flatten() for xi in np.meshgrid(*x_data_coordinates.values(), indexing="ij") ] x_data_structured = { k: xi for k, xi in zip(x_data.keys(), x_data_structured_values) } y_data_structured = interpolator( np.stack(tuple(x_data_structured_values), axis=1) ) y_data_structured = y_data_structured.reshape([ np.length(xi) for xi in x_data_coordinates.values() ]) interpolated_model_kwargs = { "fill_value": fill_value, **interpolated_model_kwargs } super().__init__( x_data_coordinates=x_data_coordinates, y_data_structured=y_data_structured, **interpolated_model_kwargs, ) self.x_data_raw_unstructured = x_data self.y_data_raw = y_data
import aerosandbox as asb import aerosandbox.numpy as np if __name__ == '__main__': af = asb.Airfoil("dae11") af.generate_polars() alpha = np.linspace(-40, 40, 300) re = np.geomspace(1e4, 1e12, 100) Alpha, Re = np.meshgrid(alpha, re) af.CL_function(alpha=0, Re=1e6) CL = af.CL_function(Alpha.flatten(), Re.flatten()).reshape(Alpha.shape) CD = af.CD_function(Alpha.flatten(), Re.flatten()).reshape(Alpha.shape) CM = af.CM_function(Alpha.flatten(), Re.flatten()).reshape(Alpha.shape) ##### Plot alpha-Re contours from aerosandbox.tools.pretty_plots import plt, show_plot, contour fig, ax = plt.subplots() contour(Alpha, Re, CL, levels=30, colorbar_label=r"$C_L$") plt.scatter(af.xfoil_data["alpha"], af.xfoil_data["Re"], color="k", alpha=0.2) plt.yscale('log') show_plot( f"Auto-generated Polar for {af.name} Airfoil", "Angle of Attack [deg]", "Reynolds Number [-]", )
plt.plot(alpha, aero["CD"]) plt.xlabel(r"$\alpha$ [deg]") plt.ylabel(r"$C_D$") set_ticks(5, 1, 0.05, 0.01) plt.ylim(bottom=0) plt.sca(ax[1, 0]) plt.plot(alpha, aero["Cm"]) plt.xlabel(r"$\alpha$ [deg]") plt.ylabel(r"$C_m$") set_ticks(5, 1, 0.5, 0.1) plt.sca(ax[1, 1]) plt.plot(alpha, aero["CL"] / aero["CD"]) plt.xlabel(r"$\alpha$ [deg]") plt.ylabel(r"$C_L/C_D$") set_ticks(5, 1, 10, 2) show_plot("`asb.AeroBuildup` Aircraft Aerodynamics") fig, ax = plt.subplots(figsize=(7, 6)) Beta, Alpha = np.meshgrid(np.linspace(-90, 90, 200), np.linspace(-90, 90, 200)) aero = AeroBuildup( airplane=airplane, op_point=OperatingPoint(velocity=10, alpha=Alpha, beta=Beta), ).run() contour(Beta, Alpha, aero["CL"], levels=30) equal() show_plot("AeroBuildup", r"$\beta$ [deg]", r"$\alpha$ [deg]")
sigma_end=sigma[i + 1], ) if i == 0: u_field = u v_field = v else: u_field += u v_field += v return u_field, v_field if __name__ == '__main__': X, Y = np.meshgrid( np.linspace(-2, 2, 50), np.linspace(-2, 2, 50), ) X = X.flatten() Y = Y.flatten() x_panels = np.array([1, -1, -1, 1, 1]) y_panels = np.array([1, 1, -1, -1, 1]) U, V = calculate_induced_velocity_line_singularities( x_field=X, y_field=Y, x_panels=x_panels, y_panels=y_panels, gamma=1 * np.ones_like(x_panels), sigma=1 * np.ones_like(x_panels))
) if i == 0: u_field = u v_field = v else: u_field += u v_field += v return u_field, v_field if __name__ == '__main__': X, Y = np.meshgrid( np.linspace(-1, 1, 50), np.linspace(-1, 1, 50), indexing='ij', ) X = X.flatten() Y = Y.flatten() U, V = calculate_induced_velocity_line_singularities( x_field=X, y_field=Y, x_panels=np.array([-0.5, 0.5, 0.5, -0.5, -0.5]), y_panels=np.array([-0.5, -0.5, 0.5, 0.5, -0.5]), gamma=np.array([0, 0, 0, 0, 0]), sigma=np.array([1, 1, 1, 1, 1])) import matplotlib.pyplot as plt import seaborn as sns
def __init__(self, x_data_coordinates: Union[np.ndarray, Dict[str, np.ndarray]], y_data_structured: np.ndarray, method: str = "bspline", fill_value=np.NaN, # Default behavior: return NaN for all inputs outside data range. ): """ Create the interpolator. Note that data must be structured (i.e., gridded on a hypercube) for general N-dimensional interpolation. Args: x_data_coordinates: The coordinates of each axis of the cube; essentially, the independent variable(s): * For the general N-dimensional case, this should be a dictionary where the keys are axis names [str] and the values are 1D arrays. * For the 1D case, you can optionally alternatively supply this as a single 1D array. Usage example for how you might generate this data, along with `y_data_structured`: >>> x1 = np.linspace(0, 5, 11) >>> x2 = np.linspace(0, 10, 21) >>> X1, X2 = np.meshgrid(x1, x2, indexing="ij") >>> >>> x_data_coordinates = { >>> "x1": x1, # 1D ndarray of length 11 >>> "x2": x2, # 1D ndarray of length 21 >>> } >>> y_data_structured = function_to_approximate(X1, X2) # 2D ndarray of shape (11, 21) y_data_structured: The dependent variable, expressed as a structured data "cube": * For the general N-dimensional case, this should be a single N-dimensional array with axis lengths corresponding to the inputs in `x_data_coordinates`. In the 1-dimensional case, this naturally reduces down to a single 1D ndarray. See usage example along with `x_data_coordinates` above. method: The method of interpolation to perform. Options: * "bspline" (Note: differentiable and suitable for optimization - made of piecewise-cubics. For other applications, other interpolators may be faster. Not monotonicity-preserving - may overshoot. Watch out for Runge's phenomenon; on that note, if your data is noisy, consider smoothing it first.) * "linear" (Note: differentiable, but not suitable for use in optimization w/o subgradient treatment due to C1-discontinuity) * "nearest" (Note: NOT differentiable, don't use in optimization. Fast.) fill_value: Gives the value that the interpolator should return for points outside of the interpolation domain. The interpolation domain is defined as the hypercube bounded by the coordinates specified in `x_data_coordinates`. If fill_value is None, then the interpolator will attempt to extrapolate if the interpolation method allows. """ try: x_data_coordinates_values = x_data_coordinates.values() except AttributeError: # If x_data_coordinates is not a dict x_data_coordinates_values = tuple([x_data_coordinates]) ### Validate inputs for coordinates in x_data_coordinates_values: if len(coordinates.shape) != 1: raise ValueError(""" `x_data_coordinates` must be either: * In the general N-dimensional case, a dict where values are 1D ndarrays defining the coordinates of each axis. * In the 1D case, can also be a 1D ndarray. """) implied_y_data_shape = tuple(len(coordinates) for coordinates in x_data_coordinates_values) if not y_data_structured.shape == implied_y_data_shape: raise ValueError(f""" The shape of `y_data_structured` should be {implied_y_data_shape} """) ### Store data self.x_data_coordinates = x_data_coordinates self.x_data_coordinates_values = x_data_coordinates_values self.y_data_structured = y_data_structured self.method = method self.fill_value = fill_value ### Create unstructured versions of the data for plotting, etc. x_data = x_data_coordinates if isinstance(x_data, dict): x_data_values = np.meshgrid(*x_data_coordinates_values, indexing="ij") x_data = { k: v.reshape(-1) for k, v in zip(x_data_coordinates.keys(), x_data_values) } self.x_data = x_data self.y_data = np.ravel(y_data_structured, order="F")
interp = UnstructuredInterpolatedModel( x_data={ "x": X.flatten(), "y": Y.flatten(), }, y_data=f.flatten() ) from aerosandbox.tools.pretty_plots import plt, show_plot fig = plt.figure() ax = fig.add_subplot(projection='3d') # ax.plot_surface(X, Y, f, color="blue", alpha=0.2) ax.scatter(X.flatten(), Y.flatten(), f.flatten()) X_plot, Y_plot = np.meshgrid( np.linspace(X.min(), X.max(), 500), np.linspace(Y.min(), Y.max(), 500), ) F_plot = interp({ "x": X_plot.flatten(), "y": Y_plot.flatten() }).reshape(X_plot.shape) ax.plot_surface( X_plot, Y_plot, F_plot, color="red", edgecolors=(1, 1, 1, 0.5), linewidth=0.5, alpha=0.2, rcount=40, ccount=40, shade=True, )
def display_graph(n_clicks, alpha, height, streamline_density, operating_checklist, *kulfan_inputs): ### Figure out if a button was pressed global n_clicks_last if n_clicks is None: n_clicks = 0 analyze_button_pressed = n_clicks > n_clicks_last n_clicks_last = n_clicks ### Parse the checklist ground_effect = "ground_effect" in operating_checklist ### Start constructing the figure airfoil = asb.Airfoil(coordinates=asb.get_kulfan_coordinates( lower_weights=np.array(kulfan_inputs[n_kulfan_inputs_per_side:]), upper_weights=np.array(kulfan_inputs[:n_kulfan_inputs_per_side]), TE_thickness=0, enforce_continuous_LE_radius=False, n_points_per_side=200, )) ### Do coordinates output coordinates_output = "\n".join( ["```"] + ["AeroSandbox Airfoil"] + ["\t%f\t%f" % tuple(coordinate) for coordinate in airfoil.coordinates] + ["```"]) ### Continue doing the airfoil things airfoil = airfoil.rotate(angle=-np.radians(alpha)) airfoil = airfoil.translate(0, height + 0.5 * np.sind(alpha)) fig = go.Figure() fig.add_trace( go.Scatter( x=airfoil.x(), y=airfoil.y(), mode="lines", name="Airfoil", fill="toself", line=dict(color="blue"), )) ### Default text output text_output = 'Click "Analyze" to compute aerodynamics!' xrng = (-0.5, 1.5) yrng = (-0.6, 0.6) if not ground_effect else (0, 1.2) if analyze_button_pressed: analysis = asb.AirfoilInviscid( airfoil=airfoil.repanel(50), op_point=asb.OperatingPoint( velocity=1, alpha=0, ), ground_effect=ground_effect, ) x = np.linspace(*xrng, 100) y = np.linspace(*yrng, 100) X, Y = np.meshgrid(x, y) u, v = analysis.calculate_velocity(x_field=X.flatten(), y_field=Y.flatten()) U = u.reshape(X.shape) V = v.reshape(Y.shape) streamline_fig = ff.create_streamline( x, y, U, V, arrow_scale=1e-16, density=streamline_density, line=dict(color="#ff82a3"), name="Streamlines", ) fig = go.Figure(data=streamline_fig.data + fig.data) text_output = make_table( pd.DataFrame({ "Engineering Quantity": ["C_L"], "Value": [f"{analysis.Cl:.3f}"] })) fig.update_layout( xaxis_title="x/c", yaxis_title="y/c", showlegend=False, yaxis=dict(scaleanchor="x", scaleratio=1), margin={"t": 0}, title=None, ) fig.update_xaxes(range=xrng) fig.update_yaxes(range=yrng) return fig, text_output, [coordinates_output]
def draw_streamlines(self, res=200, show=True): fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8), dpi=200) plt.xlim(-0.5, 1.5) plt.ylim(-0.5, 0.5) xrng = np.diff(np.array(ax.get_xlim())) yrng = np.diff(np.array(ax.get_ylim())) x = np.linspace(*ax.get_xlim(), int(np.round(res * xrng / yrng))) y = np.linspace(*ax.get_ylim(), res) X, Y = np.meshgrid(x, y) shape = X.shape X = X.flatten() Y = Y.flatten() U, V = self.calculate_velocity(X, Y) X = X.reshape(shape) Y = Y.reshape(shape) U = U.reshape(shape) V = V.reshape(shape) # NaN out any points inside the airfoil for airfoil in self.airfoils: contains = airfoil.contains_points(X, Y) U[contains] = np.NaN V[contains] = np.NaN speed = (U**2 + V**2)**0.5 Cp = 1 - speed**2 ### Draw the airfoils for airfoil in self.airfoils: plt.fill(airfoil.x(), airfoil.y(), "k", linewidth=0, zorder=4) from palettable.colorbrewer.diverging import RdBu_4 as colormap plt.streamplot( x, y, U, V, color=speed, density=2.5, arrowsize=0, cmap=colormap.mpl_colormap, ) CB = plt.colorbar( orientation="horizontal", shrink=0.8, aspect=40, ) CB.set_label(r"Relative Airspeed ($U/U_\infty$)") plt.clim(0.4, 1.6) plt.gca().set_aspect('equal', adjustable='box') plt.xlabel(r"$x/c$") plt.ylabel(r"$y/c$") plt.title(rf"Invisicid Airfoil: Flow Field") plt.tight_layout() if show: plt.show()
import aerosandbox as asb import aerosandbox.numpy as np from scipy import io from pathlib import Path root = Path(__file__).parent # %% data = io.loadmat(str(root / "data" / "wind_data_99.mat")) lats_v = data["lats"].flatten() alts_v = data["alts"].flatten() speeds = data["speeds"].reshape(len(alts_v), len(lats_v)).T.flatten() lats, alts = np.meshgrid(lats_v, alts_v, indexing="ij") lats = lats.flatten() alts = alts.flatten() # %% lats_scaled = (lats - 37.5) / 11.5 alts_scaled = (alts - 24200) / 24200 speeds_scaled = (speeds - 7) / 56 alt_diff = np.diff(alts_v) alt_diff_aug = np.hstack((alt_diff[0], alt_diff, alt_diff[-1])) weights_1d = (alt_diff_aug[:-1] + alt_diff_aug[1:]) / 2 weights_1d = weights_1d / np.mean(weights_1d) # region_of_interest = np.logical_and( # alts_v > 10000,