deeplearning/modulus/modulus-sym-v120/_modules/modulus/sym/geometry/curve.html

Sym v1.2.0

Source code for modulus.sym.geometry.curve

# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Defines different Curve objects
"""

import types
import numpy as np
import sympy
import symengine
from chaospy.distributions.sampler.sequences.primes import create_primes
from chaospy.distributions.sampler.sequences.van_der_corput import (
    create_van_der_corput_samples as create_samples,
)

from modulus.sym.utils.sympy import np_lambdify
from .parameterization import Parameterization, Parameter
from .helper import _sympy_func_to_func


[docs]class Curve: """A Curve object that keeps track of the surface/perimeter of a geometry. The curve object also contains normals and area/length of curve. """ def __init__(self, sample, dims, parameterization=Parameterization()): # store attributes self._sample = sample self._dims = dims self.parameterization = parameterization def sample( self, nr_points, criteria=None, parameterization=None, quasirandom=False ): # use internal parameterization if not given if parameterization is None: parameterization = self.parameterization # continually sample points throwing out points that don't satisfy criteria invar = { key: np.empty((0, 1)) for key in self.dims + ["normal_" + x for x in self.dims] + ["area"] } params = {key: np.empty((0, 1)) for key in parameterization.parameters} total_sampled = 0 total_tried = 0 nr_try = 0 while True: # sample curve local_invar, local_params = self._sample( nr_points, parameterization, quasirandom ) # compute given criteria and remove points if criteria is not None: computed_criteria = criteria(local_invar, local_params) local_invar = { key: value[computed_criteria[:, 0], :] for key, value in local_invar.items() } local_params = { key: value[computed_criteria[:, 0], :] for key, value in local_params.items() } # store invar for key in local_invar.keys(): invar[key] = np.concatenate([invar[key], local_invar[key]], axis=0) # store params for key in local_params.keys(): params[key] = np.concatenate([params[key], local_params[key]], axis=0) # keep track of sampling total_sampled = next(iter(invar.values())).shape[0] total_tried += nr_points nr_try += 1 # break when finished sampling if total_sampled >= nr_points: for key, value in invar.items(): invar[key] = value[:nr_points] for key, value in params.items(): params[key] = value[:nr_points] break # check if couldn't sample if nr_try > 1000 and total_sampled < 1: raise Exception("Unable to sample curve") return invar, params @property def dims(self): """ Returns ------- dims : list of strings output can be ['x'], ['x','y'], or ['x','y','z'] """ return ["x", "y", "z"][: self._dims]
[docs] def approx_area( self, parameterization=Parameterization(), criteria=None, approx_nr=10000, quasirandom=False, ): """ Parameters ---------- parameterization: dict with of Parameters and their ranges If the curve is parameterized then you can provide ranges for the parameters with this. criteria : None, SymPy boolean exprs Calculate area discarding regions that don't satisfy this criteria. approx_nr : int Area might be difficult to compute if parameterized. In this case the area is approximated by sampling `area`, `approx_nr` number of times. This amounts to monte carlo integration. Returns ------- area : float area of curve """ s, p = self._sample( nr_points=approx_nr, parameterization=parameterization, quasirandom=quasirandom, ) computed_criteria = criteria(s, p) total_area = np.sum(s["area"][computed_criteria[:, 0], :]) return total_area
[docs] def scale(self, x, parameterization=Parameterization()): """ scale curve Parameters ---------- x : float, SymPy Symbol/Exprs scale factor. """ def _sample(internal_sample, dims, x): if isinstance(x, (float, int)): pass elif isinstance(x, sympy.Basic): x = _sympy_func_to_func(x) else: raise TypeError("Scaling by type " + str(type(x)) + "is not supported") def sample( nr_points, parameterization=Parameterization(), quasirandom=False ): # sample points invar, params = internal_sample( nr_points, parameterization, quasirandom ) # compute scale if needed if isinstance(x, (float, int)): computed_scale = x else: computed_scale = s(params) # scale invar for d in dims: invar[d] *= x invar["area"] *= x ** (len(dims) - 1) return invar, params return sample return Curve( _sample(self._sample, self.dims, x), len(self.dims), self.parameterization.union(parameterization), )
[docs] def translate(self, xyz, parameterization=Parameterization()): """ translate curve Parameters ---------- xyz : tuple of floats, ints, SymPy Symbol/Exprs translate curve by these values. """ def _sample(internal_sample, dims, xyz): compiled_xyz = [] for i, x in enumerate(xyz): if isinstance(x, (float, int)): compiled_xyz.append(x) elif isinstance(x, sympy.Basic): compiled_xyz.append(_sympy_func_to_func(x)) else: raise TypeError( "Translate by type " + str(type(x)) + "is not supported" ) def sample( nr_points, parameterization=Parameterization(), quasirandom=False ): # sample points invar, params = internal_sample( nr_points, parameterization, quasirandom ) # compute translation if needed computed_translation = [] for x in compiled_xyz: if isinstance(x, (float, int)): computed_translation.append(x) else: computed_translation.append(x(params)) # translate invar for d, x in zip(dims, computed_translation): invar[d] += x return invar, params return sample return Curve( _sample(self._sample, self.dims, xyz), len(self.dims), self.parameterization.union(parameterization), )
[docs] def rotate(self, angle, axis, parameterization=Parameterization()): """ rotate curve Parameters ---------- x : float, SymPy Symbol/Exprs scale factor. """ def _sample(internal_sample, dims, angle, axis): if isinstance(angle, (float, int)): pass elif isinstance(angle, sympy.Basic): angle = _sympy_func_to_func(angle) else: raise TypeError( "Scaling by type " + str(type(angle)) + "is not supported" ) def sample( nr_points, parameterization=Parameterization(), quasirandom=False ): # sample points invar, params = internal_sample( nr_points, parameterization, quasirandom ) # compute translation if needed if isinstance(angle, (float, int)): computed_angle = angle else: computed_angle = angle(params) # angle invar rotated_invar = {**invar} rotated_dims = [key for key in self.dims if key != axis] rotated_invar[rotated_dims[0]] = ( np.cos(computed_angle) * invar[rotated_dims[0]] - np.sin(computed_angle) * invar[rotated_dims[1]] ) rotated_invar["normal_" + rotated_dims[0]] = ( np.cos(computed_angle) * invar["normal_" + rotated_dims[0]] - np.sin(computed_angle) * invar["normal_" + rotated_dims[1]] ) rotated_invar[rotated_dims[1]] = ( np.sin(computed_angle) * invar[rotated_dims[0]] + np.cos(computed_angle) * invar[rotated_dims[1]] ) rotated_invar["normal_" + rotated_dims[1]] = ( np.sin(computed_angle) * invar["normal_" + rotated_dims[0]] + np.cos(computed_angle) * invar["normal_" + rotated_dims[1]] ) return rotated_invar, params return sample return Curve( _sample(self._sample, self.dims, angle, axis), len(self.dims), self.parameterization.union(parameterization), )

def invert_normal(self): def _sample(internal_sample, dims): def sample( nr_points, parameterization=Parameterization(), quasirandom=False ): s, p = internal_sample(nr_points, parameterization, quasirandom) for d in dims: s["normal_" + d] = -s["normal_" + d] return s, p return sample return Curve( _sample(self._sample, self.dims), len(self.dims), self.parameterization )

[docs]class SympyCurve(Curve): """Curve defined by sympy functions Parameters ---------- functions : dictionary of SymPy Exprs Parameterized curve in 1, 2 or 3 dimensions. For example, a circle might have:: functions = {'x': cos(theta), \t'y': sin(theta), \t'normal_x': cos(theta), \t'normal_y': sin(theta)} TODO: refactor to remove normals. ranges : dictionary of Sympy Symbols and ranges This gives the ranges for the parameters in the parameterized curve. For example, a circle might have `ranges = {theta: (0, 2*pi)}`. area : float, int, SymPy Exprs The surface area/perimeter of the curve. criteria : SymPy Boolean Function If this boolean expression is false then we do not sample their on curve. This can be used to enforce uniform sample probability. """ def __init__(self, functions, parameterization, area, criteria=None): # lambdify functions lambdify_functions = {} for key, func in functions.items(): try: func = float(func) except: pass if isinstance(func, float): lambdify_functions[key] = float(func) elif isinstance(func, (sympy.Basic, symengine.Basic, Parameter)): lambdify_functions[key] = _sympy_func_to_func(func) else: raise TypeError("function type not supported: " + str(type(func))) # lambdify area function try: area = float(area) except: pass if isinstance(area, float): area_fn = float(area) elif isinstance(area, (sympy.Basic, symengine.Basic, Parameter)): area_fn = _sympy_func_to_func(area) else: raise TypeError("area type not supported: " + str(type(area))) lambdify_functions["area"] = area_fn # lambdify criteria function if criteria is not None: criteria = _sympy_func_to_func(criteria) # create closure for sample function def _sample(lambdify_functions, criteria, internal_parameterization): def sample( nr_points, parameterization=Parameterization(), quasirandom=False ): # use internal parameterization if not given i_parameterization = internal_parameterization.copy() for key, value in parameterization.param_ranges.items(): i_parameterization.param_ranges[key] = value # continually sample points throwing out points that don't satisfy criteria invar = { str(key): np.empty((0, 1)) for key in lambdify_functions.keys() } params = { str(key): np.empty((0, 1)) for key in parameterization.param_ranges.keys() } total_sampled = 0 total_tried = 0 nr_try = 0 while True: # sample parameter ranges local_params = i_parameterization.sample(nr_points, quasirandom) # compute curve points from functions local_invar = {} for key, func in lambdify_functions.items(): if isinstance(func, (float, int)): local_invar[key] = np.full_like( next(iter(local_params.values())), func ) else: local_invar[key] = func(local_params) local_invar["area"] /= next(iter(local_params.values())).shape[0] # remove points that don't satisfy curve criteria if needed if criteria is not None: # compute curve criteria computed_criteria = criteria(local_params).astype(bool) # remove elements points based on curve criteria local_invar = { key: value[computed_criteria[:, 0], :] for key, value in local_invar.items() } local_params = { key: value[computed_criteria[:, 0], :] for key, value in local_params.items() } # only store external parameters for key in list(local_params.keys()): if key not in parameterization.parameters: local_params.pop(key) # store invar for key in local_invar.keys(): invar[key] = np.concatenate( [invar[key], local_invar[key]], axis=0 ) # store params for key in local_params.keys(): params[key] = np.concatenate( [params[key], local_params[key]], axis=0 ) # keep track of sampling total_sampled = next(iter(invar.values())).shape[0] total_tried += next(iter(local_invar.values())).shape[0] nr_try += 1 # break when finished sampling if total_sampled >= nr_points: for key, value in invar.items(): invar[key] = value[:nr_points] for key, value in params.items(): params[key] = value[:nr_points] break # check if couldn't sample if nr_try > 10000 and total_sampled < 1: raise Exception("Unable to sample curve") return invar, params return sample # initialize curve Curve.__init__( self, _sample(lambdify_functions, criteria, parameterization), len(functions) // 2, parameterization=parameterization, )
© Copyright 2023, NVIDIA Modulus Team. Last updated on Jan 25, 2024.