def _getBBox(self, comp): """ this function computes the bounding box of the component. We add some buffer on each direction because we will use this bbox to determine which components to project points while adding point sets """ # initialize the array bbox = np.zeros((3, 2)) # we need to get the number of main surfaces on this geometry nSurf = openvsp.GetNumMainSurfs(comp) nuv = self.bboxuv.shape[0] # allocate the arrays nodes = np.zeros((nSurf * nuv, 3)) # loop over the surfaces for iSurf in range(nSurf): offset = iSurf * nuv # evaluate the points ptVec = openvsp.CompVecPnt01(comp, iSurf, self.bboxuv[:, 0], self.bboxuv[:, 1]) # now extract the coordinates from the vec3dvec...sigh... for i in range(nuv): nodes[offset + i, :] = (ptVec[i].x(), ptVec[i].y(), ptVec[i].z()) # get the min/max values of the coordinates for i in range(3): # this might be faster if we specify row/column major bbox[i, 0] = nodes[:, i].min() bbox[i, 1] = nodes[:, i].max() # finally scale the bounding box and return bbox *= self.vspScale # also give some offset on all directions bbox[:, 0] -= 0.1 bbox[:, 1] += 0.1 return bbox.copy()
def test_1(self, train=False, refDeriv=False): """ Test 1: Basic OpenVSP sphere test A sphere centered at 1, 0, 0 but strech scales from the origin 0, 0, 0 """ def sample_uv(nu, nv): # function to create sample uv from the surface and save these points. u = np.linspace(0, 1, nu) v = np.linspace(0, 1, nv) uu, vv = np.meshgrid(u, v) uv = np.array((uu.flatten(), vv.flatten())) return uv refFile = os.path.join(self.base_path, "ref/test_DVGeometryVSP_01.ref") with BaseRegTest(refFile, train=train) as handler: handler.root_print("Test 1: Basic OpenVSP sphere") vspFile = os.path.join(self.base_path, "../../input_files/simpleEll_med.vsp3") DVGeo = DVGeometryVSP(vspFile) dh = 0.1 # we have a sphere centered at x,y,z = 1, 0, 0 with radius 1 # add design variables for the radius values in x-y-z DVGeo.addVariable("Ellipsoid", "Design", "A_Radius", lower=0.5, upper=3.0, scale=1.0, dh=dh) DVGeo.addVariable("Ellipsoid", "Design", "B_Radius", lower=0.5, upper=3.0, scale=1.0, dh=dh) DVGeo.addVariable("Ellipsoid", "Design", "C_Radius", lower=0.5, upper=3.0, scale=1.0, dh=dh) # dictionary of design variables x = DVGeo.getValues() nDV = len(x) dvList = list(x.keys()) # add some known points to the sphere points = [ [0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 0.0], [1.0, -1.0, 0.0], [1.0, 0.0, 1.0], [1.0, 0.0, -1.0], ] pointSet1 = np.array(points) nPts = len(points) dMax_global = DVGeo.addPointSet(pointSet1, "known_points") handler.assert_allclose(dMax_global, 0.0, name="pointset1_projection_tol", rtol=1e0, atol=1e-10) # add some random points # randomly generate points nPts = 100 # just get nPts ^2 points uv = sample_uv(10, 10) # now lets get the coordinates of these uv combinations ptvec = openvsp.CompVecPnt01(DVGeo.allComps[0], 0, uv[0, :], uv[1, :]) # convert the ptvec into list of coordinates points = [] radii = [] for pt in ptvec: # print (pt) points.append([pt.x(), pt.y(), pt.z()]) radius = ((pt.x() - 1.0)**2 + pt.y()**2 + pt.z()**2)**0.5 radii.append(radius) pointSet2 = np.array(points) handler.assert_allclose(np.array(radii), 1.0, name="pointset2_diff_from_sphere", rtol=1e-3, atol=1e-3) dim = 3 # add this point set since our points EXACTLY lie on the sphere, we should get 0 distance in the # projections to machine precision dMax_global = DVGeo.addPointSet(pointSet2, "generated_points") handler.assert_allclose(dMax_global, 0.0, name="pointset1_projection_tol", rtol=1e0, atol=1e-15) # lets get the gradients wrt design variables. For this we can define our dummy jacobian for dIdpt # that is an (N, nPts, 3) array. We will just monitor how each component in each point changes so # we will need nDV*dim*nPts functions of interest. dIdpt = np.zeros((nPts * dim, nPts, dim)) # first 3*nDV entries will correspond to the first point's x,y,z direction for i in range(nPts): for j in range(dim): # initialize this seed to 1 dIdpt[dim * i + j, i, j] = 1.0 # get the total sensitivities funcSens = DVGeo.totalSensitivity(dIdpt, "generated_points") # lets read variables from the total sensitivities and check maxError = 1e-20 # loop over our pointset for i in range(nPts): point = pointSet2[i, :] # we have 3 DVs, radius a,b, and c. These should only change coordinates in the x,y,z directions for j in range(nDV): dv = dvList[j] # loop over coordinates for k in range(dim): if k == j: # this sensitivity should be equal to the "i"th coordinate of the original point. error = abs(funcSens[dv][dim * i + k] - point[j]) else: # this sensitivity should be zero. error = abs(funcSens[dv][dim * i + k]) # print('Error for dv %s on the %d th coordinate of point at (%1.1f, %1.1f, %1.1f) is = %1.16f'%(dv, k+1, point[0],point[1],point[2], error )) maxError = max(error, maxError) handler.assert_allclose(maxError, 0.0, "sphere_derivs", rtol=1e0, atol=1e-14)
def _getQuads(self): # build the quad mesh using the internal vsp geometry nSurf = len(self.allComps) pts = np.zeros((0, 3)) conn = np.zeros((0, 4), dtype="intc") sizes = np.zeros((nSurf, 2), "intc") cumSizes = np.zeros(nSurf + 1, "intc") uv = [] # this will hold tessalation points offset = 0 gind = 0 for geom in self.allComps: # get uv tessalation utess, wtess = openvsp.GetUWTess01(geom, 0) # check if these values are good, otherwise, do it yourself! # save these values uv.append([np.array(utess), np.array(wtess)]) nu = len(utess) nv = len(wtess) nElem = (nu - 1) * (nv - 1) # get u,v combinations of nodes uu, vv = np.meshgrid(utess, wtess) utess = uu.flatten() wtess = vv.flatten() # get the points ptvec = openvsp.CompVecPnt01(geom, 0, utess, wtess) # number of nodes for this geometry curSize = len(ptvec) # initialize coordinate and connectivity arrays compPts = np.zeros((curSize, 3)) compConn = np.zeros((nElem, 4), dtype="intc") # get the coordinates of the points for i in range(curSize): compPts[i, :] = (ptvec[i].x(), ptvec[i].y(), ptvec[i].z()) # build connectivity array k = 0 for j in range(nv - 1): for i in range(nu - 1): compConn[k, 0] = j * nu + i compConn[k, 1] = j * nu + i + 1 compConn[k, 2] = (j + 1) * nu + i + 1 compConn[k, 3] = (j + 1) * nu + i k += 1 # apply the offset to the connectivities compConn += offset # stack the results pts = np.vstack((pts, compPts)) conn = np.vstack((conn, compConn)) # number of u and v point count sizes[gind, :] = (nu, nv) # cumilative number of points cumSizes[gind + 1] = cumSizes[gind] + curSize # increment the offset offset += curSize # increment geometry index gind += 1 # finally, scale the points and save the data self.pts0 = pts * self.vspScale self.conn = conn self.sizes = sizes self.cumSizes = cumSizes self.uv = uv
def test_2(self, train=False, refDeriv=False): """ Test 2: OpenVSP wing test """ # we skip parallel tests for now if not train and self.N_PROCS > 1: self.skipTest("Skipping the parallel test for now.") def sample_uv(nu, nv): # function to create sample uv from the surface and save these points. u = np.linspace(0, 1, nu + 1) v = np.linspace(0, 1, nv + 1) uu, vv = np.meshgrid(u, v) # print (uu.flatten(), vv.flatten()) uv = np.array((uu.flatten(), vv.flatten())) return uv refFile = os.path.join(self.base_path, "ref/test_DVGeometryVSP_02.ref") with BaseRegTest(refFile, train=train) as handler: handler.root_print("Test 2: OpenVSP NACA 0012 wing") vspFile = os.path.join(self.base_path, "../../input_files/naca0012.vsp3") DVGeo = DVGeometryVSP(vspFile) dh = 1e-6 openvsp.ClearVSPModel() openvsp.ReadVSPFile(vspFile) geoms = openvsp.FindGeoms() DVGeo = DVGeometryVSP(vspFile) comp = "WingGeom" # loop over sections # normally, there are 9 sections so we should loop over range(9) for the full test # to have it run faster, we just pick 2 sections for i in [0, 5]: # Twist DVGeo.addVariable(comp, "XSec_%d" % i, "Twist", lower=-10.0, upper=10.0, scale=1e-2, scaledStep=False, dh=dh) # loop over coefs # normally, there are 7 coeffs so we should loop over range(7) for the full test # to have it run faster, we just pick 2 sections for j in [0, 4]: # CST Airfoil shape variables group = "UpperCoeff_%d" % i var = "Au_%d" % j DVGeo.addVariable(comp, group, var, lower=-0.1, upper=0.5, scale=1e-3, scaledStep=False, dh=dh) group = "LowerCoeff_%d" % i var = "Al_%d" % j DVGeo.addVariable(comp, group, var, lower=-0.5, upper=0.1, scale=1e-3, scaledStep=False, dh=dh) # now lets generate ourselves a quad mesh of these cubes. uv_g = sample_uv(8, 8) # total number of points ntot = uv_g.shape[1] # rank on this proc rank = MPI.COMM_WORLD.rank # first, equally divide nuv = ntot // MPI.COMM_WORLD.size # then, add the remainder if rank < ntot % MPI.COMM_WORLD.size: nuv += 1 # allocate the uv array on this proc uv = np.zeros((2, nuv)) # print how mant points we have MPI.COMM_WORLD.Barrier() # loop over the points and save all that this proc owns ii = 0 for i in range(ntot): if i % MPI.COMM_WORLD.size == rank: uv[:, ii] = uv_g[:, i] ii += 1 # get the coordinates nNodes = len(uv[0, :]) openvsp.CompVecPnt01(geoms[0], 0, uv[0, :], uv[1, :]) # extract node coordinates and save them in a numpy array coor = np.zeros((nNodes, 3)) for i in range(nNodes): pnt = openvsp.CompPnt01(geoms[0], 0, uv[0, i], uv[1, i]) coor[i, :] = (pnt.x(), pnt.y(), pnt.z()) # Add this pointSet to DVGeo DVGeo.addPointSet(coor, "test_points") # We will have nNodes*3 many functions of interest... dIdpt = np.zeros((nNodes * 3, nNodes, 3)) # set the seeds to one in the following fashion: # first function of interest gets the first coordinate of the first point # second func gets the second coord of first point etc.... for i in range(nNodes): for j in range(3): dIdpt[i * 3 + j, i, j] = 1 # first get the dvgeo result funcSens = DVGeo.totalSensitivity(dIdpt.copy(), "test_points") # now perturb the design with finite differences and compute FD gradients DVs = DVGeo.getValues() funcSensFD = {} for x in DVs: # perturb the design xRef = DVs[x].copy() DVs[x] += dh DVGeo.setDesignVars(DVs) # get the new points coorNew = DVGeo.update("test_points") # calculate finite differences funcSensFD[x] = (coorNew.flatten() - coor.flatten()) / dh # set back the DV DVs[x] = xRef.copy() # now loop over the values and compare # when this is run with multiple procs, VSP sometimes has a bug # that leads to different procs having different spanwise # u-v distributions. as a result, the final values can differ up to 1e-5 levels # this issue does not come up if this tests is ran with a single proc biggest_deriv = 1e-16 for x in DVs: err = np.array(funcSens[x].squeeze()) - np.array(funcSensFD[x]) maxderiv = np.max(np.abs(funcSens[x].squeeze())) normalizer = np.median(np.abs(funcSensFD[x].squeeze())) if np.abs(normalizer) < 1: normalizer = np.ones(1) normalized_error = err / normalizer if maxderiv > biggest_deriv: biggest_deriv = maxderiv handler.assert_allclose(normalized_error, 0.0, name=f"{x}_grad_normalized_error", rtol=1e0, atol=5e-5) # make sure that at least one derivative is nonzero self.assertGreater(biggest_deriv, 0.005)