《Fluid Engine Development》 學習筆記3-光滑粒子流體動力學
用粒子表示流體最熱門的方法就是就是光滑粒子流體動力學(Smoothed Particle Hydrodynamics (SPH).)
這種方法模糊了流體的邊界,用有限數量的粒子代表流體,該方法的基本思想是將視作連續的流體(或固體)用相互作用的質點組來描述,各個物質點上承載各種物理量,包括質量、速度等,通過求解質點組的動力學方程和跟蹤每個質點的運動軌道,求得整個系統的力學行為
經典核函式
SPH演算法涉及到“光滑核”的概念,可以這樣理解這個概念,粒子的屬性都會“擴散”到周圍,並且隨著距離的增加影響逐漸變小,這種隨著距離而衰減的函式被稱為“光滑核”函式,最大影響半徑為“光滑核半徑”。
書中提到的經典核函式有 $W_{std}(r) = \frac{315}{64\pi h^{3}}(1 -\frac{r^{2}}{h_{2}})^{3} (0 \leq r \leq h) $,其他情況為0
SPH插值
SPH插值的基本思想是通過查詢附近的粒子來測量任意給定位置的任何物理量。它是一個加權平均,權重是質量乘以核函式除以相鄰粒子的密度。
質量除以密度就是體積,因此這個插值,將更多的權重放在離原點更近的值上
相關程式碼實現如下
Vector3D CalfFluidEngine::SphSystemData3::Interpolate(const Vector3D & origin, const std::vector<Vector3D>& values) const { Vector3D sum = Vector3D::zero; auto& d = GetDensities(); SphStandardKernel3 kernel(_kernelRadius); const double m = GetParticleMass(); GetNeighborSearcher()->ForEachNearbyPoint( origin, _kernelRadius, [&](size_t i, const Vector3D& neighborPosition) { double dist = Vector3D::Distance(origin,neighborPosition); double weight = m / d[i] * kernel(dist); sum += weight * values[i]; } ); return sum; } double CalfFluidEngine::SphStandardKernel3::operator()(double distance) const { if (distance * distance >= h2) { return 0.0; } else { double x = 1.0 - distance * distance / h2; return 315.0 / (64.0 * kPiD * h3) * x * x * x; } } void CalfFluidEngine::PointHashGridSearcher3::ForEachNearbyPoint(const Vector3D & origin, double radius, const std::function<void(size_t, const Vector3D&)>& callback) const { if (_buckets.empty()) { return; } size_t nearbyKeys[8]; getNearbyKeys(origin, nearbyKeys); const double queryRadiusSquared = radius * radius; for (int i = 0; i < 8; i++) { const auto& bucket = _buckets[nearbyKeys[i]]; size_t numberOfPointsInBucket = bucket.size(); for (size_t j = 0; j < numberOfPointsInBucket; ++j) { size_t pointIndex = bucket[j]; double rSquared = (_points[pointIndex] - origin).SquareMagnitude(); if (rSquared <= queryRadiusSquared) { callback(pointIndex, _points[pointIndex]); } } } }
我們可以看到插值函式依賴於密度,因為粒子的位置在每個時間步長都會改變,而密度也隨之在每個時間步長都會改。
void CalfFluidEngine::SphSystemData3::UpdateDensities() { auto& p = GetPositions(); auto& d = GetDensities(); const double m = GetParticleMass(); tbb::parallel_for( tbb::blocked_range<size_t>(0, GetNumberOfParticles()), [&](const tbb::blocked_range<size_t> & b) { for (size_t i = b.begin(); i != b.end(); ++i) { double sum = SumOfKernelNearby(p[i]); d[i] = m * sum; } }); } double CalfFluidEngine::SphSystemData3::SumOfKernelNearby(const Vector3D & origin) const { double sum = 0.0; SphStandardKernel3 kernel(_kernelRadius); GetNeighborSearcher()->ForEachNearbyPoint( origin, _kernelRadius, [&](size_t, const Vector3D& neighborPosition) { double dist = Vector3D::Distance(origin, neighborPosition); sum += kernel(dist); });
梯度運算元
類似於之前的插值,梯度能用類似的方法獲得
Vector3D CalfFluidEngine::SphSystemData3::GradientAt(size_t i, const std::vector<double>& values) const
{
Vector3D sum;
auto& p = GetPositions();
auto& d = GetDensities();
const auto& neighbors = GetNeighborLists()[i];
Vector3D origin = p[i];
SphSpikyKernel3 kernel(_kernelRadius);
const double m = GetParticleMass();
for (size_t j : neighbors) {
Vector3D neighborPosition = p[j];
double dist = Vector3D::Distance(origin, neighborPosition);
if (dist > kEpsilonD) {
Vector3D dir = (neighborPosition - origin) / dist;
sum += m * values[i] / d[j] *
kernel.Gradient(dist, dir);
}
}
return sum;
}
Vector3D ...::Gradient(double distance, const Vector3D & directionToParticle) const
{
return -firstDerivative(distance) * directionToParticle;
}
然而這種梯度的實現是不對稱的,相鄰的粒子可能會因為擁有不同的價值和密度而擁有不同的梯度,這也意味著2個粒子將被施加不同的力。根據牛頓第三運動定律,每一個作用力都有一個相等且相反的作用力
為解決這個問題,需要修改梯度實現。
書所使用的公式是 \(\nabla \phi(x)= \rho _{j}m \sum_{j}(\frac{\phi_{i}}{\rho _{i} ^{2}} + \frac{\phi_{j}}{\rho _{j} ^{2}}) \nabla W(|x - x_{j}|)\)
Vector3D CalfFluidEngine::SphSystemData3::GradientAt(size_t i, const std::vector<double>& values) const
{
Vector3D sum;
auto& p = GetPositions();
auto& d = GetDensities();
const auto& neighbors = GetNeighborLists()[i];
Vector3D origin = p[i];
SphSpikyKernel3 kernel(_kernelRadius);
const double m = GetParticleMass();
for (size_t j : neighbors) {
Vector3D neighborPosition = p[j];
double dist = Vector3D::Distance(origin, neighborPosition);
if (dist > kEpsilonD) {
Vector3D dir = (neighborPosition - origin) / dist;
sum += d[i] * m *
(values[i] / (d[i] * d[i]) + values[j] / (d[j] * d[j])) *
kernel.Gradient(dist, dir);
}
}
return sum;
}
拉普拉斯運算元
類似於之前的插值,按照拉普拉斯的數學定義,嘗試計算拉普拉斯運算元,結果如下
double CalfFluidEngine::SphSystemData3::LaplacianAt(size_t i, const std::vector<double>& values) const
{
double sum = 0.0;
auto& p = GetPositions();
auto& d = GetDensities();
const auto& neighbors = GetNeighborLists()[i];
Vector3D origin = p[i];
SphSpikyKernel3 kernel(_kernelRadius);
const double m = GetParticleMass();
for (size_t j : neighbors) {
Vector3D neighborPosition = p[j];
double dist = Vector3D::Distance(origin, neighborPosition);
sum += m * values[j] / d[j] * kernel.Laplacian(dist);
}
return sum;
}
double ...::Laplacian(double distance) const
{
return secondDerivative(distance);
}
遺憾的是這般計算拉普拉斯運算元在即便所有場值都是相同的非零值時,也不會輸出零場
拉普拉斯正確的計算方法如下 \(\nabla^{2} \phi(x)=m \sum_{j}(\frac{\phi_{j} - \phi_{i}}{\rho _{j} } ) \nabla^{2} W(|x - x_{j}|)\)
double CalfFluidEngine::SphSystemData3::LaplacianAt(size_t i, const std::vector<double>& values) const
{
double sum = 0.0;
auto& p = GetPositions();
auto& d = GetDensities();
const auto& neighbors = GetNeighborLists()[i];
Vector3D origin = p[i];
SphSpikyKernel3 kernel(_kernelRadius);
const double m = GetParticleMass();
for (size_t j : neighbors) {
Vector3D neighborPosition = p[j];
double dist = Vector3D::Distance(origin, neighborPosition);
sum += m * (values[j] - values[i]) / d[j] * kernel.Laplacian(dist);
}
return sum;
}
Spiky核函式
梯度運算元是用來計算壓力梯度的,粒子太接近,壓力就會把粒子推開,然而經典核函式即使粒子越來越接近,也會出現壓力越來越小的情況,甚至還會出現負值
如下圖是原書中的圖,a是經典核函式,實線是原核函式,虛線是一階偏導,點線是二階導
為解決這個問題,Spiky核函式誕生了,如上圖b
公式為$W_{spiky}(r) = \frac{15}{\pi h^{3}}(1 -\frac{r^{3}}{h_{3}})^{3} (0 \leq r \leq h) $其他情況為0
我們插值獲取權重時使用經典核函式,計算拉普拉斯運算元和梯度時使用Spiky核函式
主體程式碼結構
這裡給出SPH系統的標頭檔案
class SphSystemSolver3 : public ParticleSystemSolver3
{
public:
SphSystemSolver3();
virtual ~SphSystemSolver3();
void SetViscosityCoefficient(
double newViscosityCoefficient) {
_viscosityCoefficient = std::max(newViscosityCoefficient, 0.0);
}
void SetPseudoViscosityCoefficient(
double newPseudoViscosityCoefficient) {
_pseudoViscosityCoefficient
= std::max(newPseudoViscosityCoefficient, 0.0);
}
void SetTimeStepLimitScale(double newScale) {
_timeStepLimitScale = std::max(newScale, 0.0);
}
std::shared_ptr<SphSystemData3> GetSphData() const;
protected:
virtual void accumulateForces(double timeIntervalInSeconds) override;
virtual void onTimeStepStart(double timeStepInSeconds) override;
virtual void onTimeStepEnd(double timeStepInSeconds) override;
virtual unsigned int getNumberOfSubTimeSteps(
double timeIntervalInSeconds) const override;
private:
void accumulateViscosityForce();
void accumulatePressureForce(double timeStepInSeconds);
void computePressure();
void accumulatePressureForce(
const std::vector<Vector3D>& positions,
const std::vector<double>& densities,
const std::vector<double>& pressures,
std::vector<Vector3D>& pressureForces);
void computePseudoViscosity(double timeStepInSeconds);
//! Exponent component of equation - of - state(or Tait's equation).
double _eosExponent = 7.0;
//! Speed of sound in medium to determin the stiffness of the system.
//! Ideally, it should be the actual speed of sound in the fluid, but in
//! practice, use lower value to trace-off performance and compressibility.
double _speedOfSound = 100.0;
//! Negative pressure scaling factor.
//! Zero means clamping. One means do nothing.
double _negativePressureScale = 0.0;
double _viscosityCoefficient = 0.01;
//Scales the max allowed time-step.
double _timeStepLimitScale = 1.0;
//! Pseudo-viscosity coefficient velocity filtering.
//! This is a minimum "safety-net" for SPH solver which is quite
//! sensitive to the parameters.
double _pseudoViscosityCoefficient = 10.0;
};
SPH系統相比正常的粒子動畫系統,重寫了accumulateForces函式和onTimeStepStart函式以及onTimeStepEnd函式,分別用以新增粘度壓力計算,更新密度,抑制噪聲
以下是accumulateForces函式的程式碼結構
void CalfFluidEngine::SphSystemSolver3::accumulateForces(double timeIntervalInSeconds)
{
ParticleSystemSolver3::accumulateForces(timeIntervalInSeconds);
accumulateViscosityForce();
accumulatePressureForce(timeIntervalInSeconds);
}
可以看到了相比粒子動畫,多了粘度和壓力的計算
以下是onTimeStepStart函式,用以更新粒子集合的密度
void CalfFluidEngine::SphSystemSolver3::onTimeStepStart(double timeStepInSeconds)
{
auto particles = GetSphData();
particles->BuildNeighborSearcher(particles->GetKernelRadius());
particles->BuildNeighborLists(particles->GetKernelRadius());
particles->UpdateDensities();
}
以下是onTimeStepEnd函式
void CalfFluidEngine::SphSystemSolver3::onTimeStepEnd(double timeStepInSeconds)
{
computePseudoViscosity(timeStepInSeconds);
}
計算壓強
狀態方程(Equation-of-State ,EOS)描述了狀態變數間的關係,我們通過狀態方程 \(p = \frac{\kappa}{\gamma}( \frac{\rho}{\rho_{0}}- 1)^{\gamma}\) 將密度對映為壓強
inline double computePressureFromEos(
double density,
double targetDensity,
double eosScale,
double eosExponent,
double negativePressureScale) {
// Equation of state
// (http://www.ifi.uzh.ch/vmml/publications/pcisph/pcisph.pdf)
double p = eosScale / eosExponent
* (std::pow((density / targetDensity), eosExponent) - 1.0);
return p;
}
觀察上公式,我們發現density 小於 targetDensity會出現負壓強的情況,而液體表面附近的確會出現密度過小的情況
為防止負壓強的引入,我們需要夾緊壓強,具體如下
inline double computePressureFromEos(
double density,
double targetDensity,
double eosScale,
double eosExponent,
double negativePressureScale) {
// Equation of state
// (http://www.ifi.uzh.ch/vmml/publications/pcisph/pcisph.pdf)
double p = eosScale / eosExponent
* (std::pow((density / targetDensity), eosExponent) - 1.0);
// Negative pressure scaling
if (p < 0) {
p *= negativePressureScale;
}
return p;
}
壓強計算程式碼如下
void CalfFluidEngine::SphSystemSolver3::computePressure()
{
auto particles = GetSphData();
size_t numberOfParticles = particles->GetNumberOfParticles();
auto& d = particles->GetDensities();
auto& p = particles->GetPressures();
// See Equation 9 from
// http://cg.informatik.uni-freiburg.de/publications/2007_SCA_SPH.pdf
const double targetDensity = particles->GetDensity();
const double eosScale
= targetDensity * (_speedOfSound * _speedOfSound) / _eosExponent;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, numberOfParticles),
[&](const tbb::blocked_range<size_t> & b) {
for (size_t i = b.begin(); i != b.end(); ++i)
{
p[i] = computePressureFromEos(
d[i],
targetDensity,
eosScale,
_eosExponent,
_negativePressureScale);
}
});
}
這裡注意到eosScale引數的計算,並不是我們想象中那樣隨便取個值,需要通過公式 $\kappa =\rho_{0} \frac{c_{s}}{\gamma} $ cs是流體中的聲速,實踐中可以用較低的值跟蹤效能。
計算壓力
\(f_{p} = - m \frac{\nabla p}{\rho}\)
回憶我們之前提到的梯度運算元計算方法,我們可以得到\(f_{p}= m^{2} \sum_{j}(\frac{p_{i}}{\rho _{i} ^{2}} + \frac{p_{j}}{\rho _{j} ^{2}}) \nabla W(|x - x_{j}|)\)
void CalfFluidEngine::SphSystemSolver3::accumulatePressureForce(const std::vector<Vector3D>& positions, const std::vector<double>& densities, const std::vector<double>& pressures, std::vector<Vector3D>& pressureForces)
{
auto particles = GetSphData();
size_t numberOfParticles = particles->GetNumberOfParticles();
double mass = particles->GetParticleMass();
const double massSquared = mass * mass;
const SphSpikyKernel3 kernel(particles->GetKernelRadius());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, numberOfParticles),
[&](const tbb::blocked_range<size_t> & b) {
for (size_t i = b.begin(); i != b.end(); ++i)
{
const auto& neighbors = particles->GetNeighborLists()[i];
for (size_t j : neighbors) {
double dist = Vector3D::Distance(positions[i], positions[j]);
if (dist > kEpsilonD) {
Vector3D dir = (positions[j] - positions[i]) / dist;
pressureForces[i] -= massSquared
* (pressures[i] / (densities[i] * densities[i])
+ pressures[j] / (densities[j] * densities[j]))
* kernel.Gradient(dist, dir);
}
}
}
});
}
計算粘度
粘度力公式為\(f_{v} = - m \mu \nabla^{2}u\) 代入之前拉普拉斯運算元的計算方法,可得公式\(\nabla^{2} \phi(x)=m^{2} \mu\sum_{j}(\frac{u_{j} - u_{i}}{\rho _{j} } ) \nabla^{2} W(|x - x_{j}|)\)
程式碼實現如下
void CalfFluidEngine::SphSystemSolver3::accumulateViscosityForce()
{
auto particles = GetSphData();
size_t numberOfParticles = particles->GetNumberOfParticles();
auto& x = particles->GetPositions();
auto& v = particles->GetVelocities();
auto& d = particles->GetDensities();
auto& f = particles->GetForces();
double mass = particles->GetParticleMass();
const double massSquared = mass * mass;
const SphSpikyKernel3 kernel(particles->GetKernelRadius());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, numberOfParticles),
[&](const tbb::blocked_range<size_t> & b) {
for (size_t i = b.begin(); i != b.end(); ++i)
{
const auto& neighbors = particles->GetNeighborLists()[i];
for (size_t j : neighbors) {
double dist = Vector3D::Distance(x[i],x[j]);
f[i] += _viscosityCoefficient * massSquared
* (v[j] - v[i]) / d[j]
* kernel.Laplacian(dist);
}
}
});
}
降低噪聲
降低噪聲的方法很簡單,以引數_pseudoViscosityCoefficient線性插值速度場和加權平均速度即可
void CalfFluidEngine::SphSystemSolver3::computePseudoViscosity(double timeStepInSeconds)
{
auto particles = GetSphData();
size_t numberOfParticles = particles->GetNumberOfParticles();
auto& x = particles->GetPositions();
auto& v = particles->GetVelocities();
auto& d = particles->GetDensities();
const double mass = particles->GetParticleMass();
const SphSpikyKernel3 kernel(particles->GetKernelRadius());
std::vector<Vector3D> smoothedVelocities(numberOfParticles);
tbb::parallel_for(
tbb::blocked_range<size_t>(0, numberOfParticles),
[&](const tbb::blocked_range<size_t> & b) {
for (size_t i = b.begin(); i != b.end(); ++i)
{
double weightSum = 0.0;
Vector3D smoothedVelocity;
const auto& neighbors = particles->GetNeighborLists()[i];
for (size_t j : neighbors) {
double dist = Vector3D::Distance(x[i],x[j]);
double wj = mass / d[j] * kernel(dist);
weightSum += wj;
smoothedVelocity += wj * v[j];
}
double wi = mass / d[i];
weightSum += wi;
smoothedVelocity += wi * v[i];
if (weightSum > 0.0) {
smoothedVelocity /= weightSum;
}
smoothedVelocities[i] = smoothedVelocity;
}
});
double factor = timeStepInSeconds * _pseudoViscosityCoefficient;
factor = Clamp(factor, 0.0, 1.0);
tbb::parallel_for(
tbb::blocked_range<size_t>(0, numberOfParticles),
[&](const tbb::blocked_range<size_t> & b) {
for (size_t i = b.begin(); i != b.end(); ++i)
{
v[i] = Lerp(
v[i], smoothedVelocities[i], factor);
}
});
}
聲速引數和時間步長
之前我們計算壓強時使用了聲速cs,為什麼會有聲速呢,因為在一個時間步長內,壓力傳播不能大於粒子核半徑h,而水中傳播的最快速度就是聲速,所以時間步長的理想步長是h/cs
最後,根據幾位科學家的研究成果,時間步長需要做如下的限制
\(\Delta t _{v} =\frac{ \lambda _{v} h}{c_{s}} ,\Delta t_{f} = \lambda_{f}\sqrt{\frac{hm}{F_{Max}}}, \Delta \leq(\Delta t_{v},\Delta t_{f})\)
\(\lambda_{v},\lambda_{f}\)是2個預設好的標量,大概0.25~0.4之間,\(F_{max}\) 是力向量的最大大小
然後時間步長因為這種限制可能會非常小,導致巨大的計算成本,而且實際上我們也無法評估最大速度和最大力是多少
為從根本解決這個問題,Solenthaler 和Pajarola提出一種預測-校正模型,消除了對聲速的依賴。這個新的模型將在下一篇筆記中闡述。
演示模擬結果
相關推薦
《Fluid Engine Development》 學習筆記3-光滑粒子流體動力學
用粒子表示流體最熱門的方法就是就是光滑粒子流體動力學(Smoothed Particle Hydrodynamics (SPH).) 這種方法模糊了流體的邊界,用有限數量的粒子代表流體,該方法的基本思想是將視作連續的流體(或固體)用相互作用的質點組來描述,各個物質點上承載各種物理量,包括質量、速度等,通過求解
《Fluid Engine Development》 學習筆記2-基礎
斷斷續續花了一個月,終於把這本書的一二兩章啃了下來,理解流體模擬的理論似乎不難,無論是《Fluid Simulation for Computer Graphics》還是《計算流體力學基礎及其應用》都能很好幫助程式設計師去理解這些原理,可在缺乏實踐情況下,這種對原理的理解其實跟死記硬背沒什麼區別。《Fluid
《Fluid Engine Development》 學習筆記4-預測校正不可壓縮SPH-PCISPH
傳統SPH方案的主要問題之一是時間步長限制。在原始的SPH中,我們首先從當前設定計算密度,使用EOS計算壓強,應用壓力梯度,然後執行時間積分。這個過程意味著只需要一定的壓縮量就可以觸發核心半徑內的壓力,從而延遲計算。因此,我們需要使用更小的時間步長(意味著更多的迭代),這在計算上是昂貴的。或者,我們可以使用不
設計模式:學習筆記(3)——命令者式
抽象 ring code 耦合度 引入 聲明 筆記 操作日誌 string Java設計模式之命令者式 引入命令模式 案列 比如我們要設計一個DOS命令模擬器,它可以接受輸入的命令並做出響應。 0.首先我們的DOS模擬器支持三個大類的功能:文件操作類(FileKit
Redis學習筆記3-Redis5個可運行程序命令的使用
運行程序 檢查 mil 數據文件 img usr pre text mod 在redis安裝文章中,說到安裝好redis後,在/usr/local/bin下有5個關於redis的可運行程序。以下關於這5個可運行程序命令的具體說明。 redis-server Redi
Git學習筆記3——工作區與暫存區,以及版本變更
暫存區 所有 cto tag clas 內容 blank 文件 set 工作區(Working Directory) 就是你在電腦裏能看到的目錄,比如我的Git_Learning文件夾就是一個工作區。 版本庫(Repository) 工作區有一個隱藏目錄.git,這個不
Python學習筆記3:簡單文件操作
name n) popu 元素 close nes pla () eof # -*- coding: cp936 -*- # 1 打開文件 # open(fileName, mode) # 參數:fileName文件名稱 # mode打開方式 # w
(MYSQL學習筆記3)mysql兩行數據合並成一行
mysql使用SUM函數,加上GROUP BY人員ID就可以實現了:SELECT SUM(PZ+CPJS+BZ+GC+SB+TG+MJ+CL+CCLW+GJ+ZL+CBZZ) as count, SUM(PZ) as PZ,SUM(CPJS) as CPJS,SUM(BZ) as BZ,SUM(GC)
AngularJs學習筆記3-服務及過濾器
聲明 運行時 維護 style 函數調用 factor blog 使用場景 需要 距離上次別博客有有一段時間了,因為最近公司和個人事情比較多,也因為學習新的知識所以耽擱了,也有人說Angularjs1.5沒有人用了,沒必要分享,我個人感覺既然開頭了我就堅持把他寫完,
java學習筆記(3)
決定 ati 開始 詳細講解 調用 數據 寄存器 art 筆記 java基礎知識 1:方法 (1)方法:就是完成特定功能的代碼塊。 註意:在很多語言裏面有函數的定義,而在Java中,函數被稱為方法。 (2)格式: 修飾符 返回值類型 方法名(參數類型 參數名1
ArcGIS API for JavaScript3.x 學習筆記[3] 加載底圖(一)【天地圖(經緯度版)】
矢量地圖 說明 tiled spa 過程 相同 服務器列表 text 服務 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5
Spark學習筆記3:鍵值對操作
對象 常用 ava java 參數 通過 頁面 ascend 處理過程 鍵值對RDD通常用來進行聚合計算,Spark為包含鍵值對類型的RDD提供了一些專有的操作。這些RDD被稱為pair RDD。pair RDD提供了並行操作各個鍵或跨節點重新進行數據分組的操作接口。 Sp
OPENCV學習筆記3-4_使用模型-視圖-控制器設計應用程序
main hold sse model getters core tpi sso data 此節介紹的架構模式(MVC),將聯合使用模型-視圖-控制器(Model-View-Controller)三個模式以及其他的類。其清晰地分離程序中的邏輯部分與用戶交互部分。此節將使
OPENCV學習筆記3-7_RGB->YCbCr
digital tor oot ssi ice buffer ica 方案 空間 色彩空間用數學方式來描述顏色集合,常見的2個基本色彩模型是RGB,YUB. ? RGB - Used in computer graphics ? YCbCr - Us
NetApp DataONTAP 集群模式 學習筆記3
netapp dataontap 集群模式DATA SVMData SVM是安全多租戶的基本單元,它能將集群分區,以程現為多個不相關的SVM。每個SVM對客戶端程現為單個獨立的服務器。除非集群管理員開啟,否則在集群中流量不會SVM之間傳輸。如果不啟用多租戶,仍然需要至少一個DataSVM。SVM之前被稱之為v
python 學習筆記3
不存在 而且 以及 lte int code 裝飾器 per err 9 高階函數 (1) map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,並把結果作為新的Iterator返回。list(map(str, [1,
37、我的C#學習筆記3
c#變量本身被用來存儲特定類型的數據,可以根據需要隨時改變變量中所存儲的數據值。變量具有名稱、類型和值。變量名是變量在程序源代碼中的標識。變量類型確定它所代表的內存的大小和類型。變量值是指它所代表的內存塊中的數據。在程序的執行過程中,變量的值可以發生變化。使用變量之前必須先聲明變量,即指定變量的類型和名稱。變
es6 學習筆記3
let 成對 ima 技術 方法 set png for ash 1、Set { let s = new Set(); s.add(1).add(2).add(5); } Set()可以去除重復成員,將只有(===)相等的會去重 (1)s.
Drools學習筆記3—Conditions / LHS—字段約束連接&字段約束操作符
mem .html 筆記 and exception .get 字段 nbsp 對象 字段約束連接 用於字段約束 對象內部多個約束連接,采用“&&”(and)、“||”(or)和“,”(and) 執行順序:“&&”(and)、“||”(or
機器學習筆記(3):多類邏輯回歸
display images 可能 https 都沒有 -s labels 明顯 交叉 仍然是 動手學嘗試學習系列的筆記,原文見:多類邏輯回歸 — 從0開始 。 這篇的主要目的,是從一堆服飾圖片中,通過機器學習識別出每個服飾圖片對應的分類是什麽(比如:一個看起來