Qt 地震剖面图(或者叫地震摆动图,波形变面积图)_地震剖面图怎么看-程序员宅基地

技术标签: QT随笔  Qt  QCustomPlot  地震剖面图  地震  地震图  

0: 项目需求

近期项目有了新的需求, 需要根据地震数据绘制出对应图表, 关于这种图的资料比较少, 翻了不少网站, 也没找到太多有用的数据, 而关于Qt的, 更是只有一篇论文. 不过搜这么多资料也不是一无所获, 最起码知道了这种图的名字. 如标题所示, 下文统一称其为地震剖面图.

1: 图形分析: 

上图是我查资料时找到的一张地震剖面图的图片, 可以看出,横轴代表通道, 纵轴代表时间, 图表中的折线按照则代表了震动的强度和方向(这一点说的可能不准确),  震动起来的部分,超出某个值的, 则将波峰染成黑色, 波谷则不做处理

2: Qt效果展示

在继续分析之前, 先看下用Qt实现的效果

 

3: 图形分解

如第一张图所示,   图中的折线, 坐标轴等, 可以用QCustomplot来实现. 关于波峰染色,  QCustomPlot在使用时, 如果设置了Brush笔刷, 那么其实就自带染色, 只不过效果可能与我们的需求有差异.

下面是一个简单的折线图, 然后加上笔刷 截图, 可以看到, 箭头指向的部分, 就是我们需要的染色效果. 但是这和我们的需求其实还是有些差距

 主要有2点

1)  这里的染色是以0为基础,  颜色值从0到波形的折线图中进行填充, 并不能控制 "振幅超出某个值以后, 进行填充的效果,  并且, 地震剖面图是有很多条曲线的, 不可能都以0为起点,必然要加上偏移量显示"

2) 图形是横着的, 地震剖面图一般都是竖着, 所以这一点也需要做点调整 

4: 首先尝试现有方案

QCustomPlot中, QGraph类有一个setChannelFillGraph函数, 可以将2个图层之间的部分进行填充, 下边进行测试

1) 先创建2个图层, 并设置数据看看

    QVector<double> x, y, y2;
    for(int i=0; i<11; i++)
    {
        x.push_back(i);

        if(i%3 == 0 || i%4 == 0 || i%8 == 0)
        {
            y.push_back(25);
        }
        else
        {
            y.push_back(10);
        }

        y2.push_back(20);

    }

    //设置交互属性, 可拖动,可缩放
    customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);

    //添加图层, 并设置画笔和笔刷颜色
    customPlot->addGraph();
    customPlot->graph(0)->setPen(QPen(Qt::black));
    customPlot->graph(0)->setBrush(QColor(255, 0, 0, 50));

    //设置数据进去
    customPlot->graph(0)->setData(x, y);


    //添加图层2, 并设置画笔和笔刷颜色
    customPlot->addGraph();
    customPlot->graph(1)->setPen(QPen(Qt::red));
    customPlot->graph(1)->setBrush(QColor(0, 0, 255, 50));

    //设置数据进去
    customPlot->graph(1)->setData(x, y2);

    //图层间填充
    customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));
    
    //设置坐标轴范围
    customPlot->xAxis->setRange(0, 10);
    customPlot->yAxis->setRange(-10, 30);

    //重绘
    customPlot->replot();

运行起来, 结果是这样的

 如图所示, 比起直接设置默认笔刷, 图层间填充确实可以实现以某条线为分界, 然后以该线为中心, 进行填充( 或许这样看和直接设置笔刷没啥区别, 但是入如果把红线的笔刷透明, 或者直接把设置图层1笔刷的代码注释掉的话, 就能看出来区别了 )

//    customPlot->graph(1)->setBrush(QColor(0, 0, 255, 50));

那么,  问题来了, 是不是按照这个思路下去, 把红线下边的填充去掉, 只把红线上方的部分进行填充. 就完成任务了?

我认为是不行的,  使用这种方式, 显示一条曲线, 需要2个图层, 并且数据量也是要翻倍的. 如果只显示一条线还好说, 显示的线条数量很多的话, 必然会导致卡顿.

 5: 源码分析

现有代码无法满足需求, 那么就只能对QCustomPlot进行二次开发了. 想要解决这个问题的第一步, 就是要找到, 笔刷填充部分的绘制逻辑

进入到QCustomplot.cpp源码中, 查找QCPGraph的 draw函数

void QCPGraph::draw(QCPPainter *painter)
{
    if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
    if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
    if (mLineStyle == lsNone && mScatterStyle.isNone()) return;

    QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments

    // loop over and draw segments of unselected/selected data:
    QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
    getDataSegments(selectedSegments, unselectedSegments);
    allSegments << unselectedSegments << selectedSegments;
    for (int i=0; i<allSegments.size(); ++i)
    {
        bool isSelectedSegment = i >= unselectedSegments.size();
        // get line pixel points appropriate to line style:
        QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care)
        getLines(&lines, lineDataRange);

        // check data validity if flag set:
#ifdef QCUSTOMPLOT_CHECK_DATA
        QCPGraphDataContainer::const_iterator it;
        for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
        {
            if (QCP::isInvalidData(it->key, it->value))
                qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
        }
#endif
        //注释的还是比较清除的, 在这里进行了图形填充的绘制
        // draw fill of graph:
        if (isSelectedSegment && mSelectionDecorator)
            mSelectionDecorator->applyBrush(painter);
        else
            painter->setBrush(mBrush);
        painter->setPen(Qt::NoPen);
        drawFill(painter, &lines);

        // draw line:
        if (mLineStyle != lsNone)
        {
            if (isSelectedSegment && mSelectionDecorator)
                mSelectionDecorator->applyPen(painter);
            else
                painter->setPen(mPen);
            painter->setBrush(Qt::NoBrush);
            if (mLineStyle == lsImpulse)
                drawImpulsePlot(painter, lines);
            else
                drawLinePlot(painter, lines); // also step plots can be drawn as a line plot
        }

        // draw scatters:
        QCPScatterStyle finalScatterStyle = mScatterStyle;
        if (isSelectedSegment && mSelectionDecorator)
            finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
        if (!finalScatterStyle.isNone())
        {
            getScatters(&scatters, allSegments.at(i));
            drawScatterPlot(painter, scatters, finalScatterStyle);
        }
    }

    // draw other selection decoration that isn't just line/scatter pens and brushes:
    if (mSelectionDecorator)
        mSelectionDecorator->drawDecoration(painter, selection());
}

上边的代码段是QCPGraph的draw函数内容, 如注释所示, 绘制填充的地方, 在drawFill函数中

那么我们进入到drawFill函数中看一下 

void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lines) const
{
    if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
    if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;

    applyFillAntialiasingHint(painter);
    const QVector<QCPDataRange> segments = getNonNanSegments(lines, keyAxis()->orientation());
    //如果没有设置与目标图层绘图的话, 就绘制一个从曲线到坐标轴的0位置的闭合图形
    if (!mChannelFillGraph)
    {
        // draw base fill under graph, fill goes all the way to the zero-value-line:
        foreach (QCPDataRange segment, segments)
            painter->drawPolygon(getFillPolygon(lines, segment));

    }
    else         //如果设置了目标图层填充, 那就绘制从当前图层到目标图层填充的闭合图形
    {
        // draw fill between this graph and mChannelFillGraph:
        QVector<QPointF> otherLines;
        mChannelFillGraph->getLines(&otherLines, QCPDataRange(0, mChannelFillGraph->dataCount()));
        if (!otherLines.isEmpty())
        {
            QVector<QCPDataRange> otherSegments = getNonNanSegments(&otherLines, mChannelFillGraph->keyAxis()->orientation());
            QVector<QPair<QCPDataRange, QCPDataRange> > segmentPairs = getOverlappingSegments(segments, lines, otherSegments, &otherLines);
            for (int i=0; i<segmentPairs.size(); ++i)
                painter->drawPolygon(getChannelFillPolygon(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second));
        }
    }
}

上边的代码段我加了注释

mChannelFillGraph 变量是从哪来的, 我们可以看一下

void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph)
{
    // prevent setting channel target to this graph itself:
    if (targetGraph == this)
    {
        qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
        mChannelFillGraph = nullptr;
        return;
    }
    // prevent setting channel target to a graph not in the plot:
    if (targetGraph && targetGraph->mParentPlot != mParentPlot)
    {
        qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
        mChannelFillGraph = nullptr;
        return;
    }
    
    /* 
        没错, 我们之前测试的时候, 进行图层间填充, 调用了 setChannelFillGraph 函数, 而这个函数
        最终会修改 mChannelFillGraph 的值
    */ 
    mChannelFillGraph = targetGraph;
}

如图所示, 如果我们调用了图层间填充的设置函数,  drawFill函数, 就会进入到 else 选项中, 否则就是绘制从曲线到坐标轴0线的填充, painter->drawPolygon(), 这个函数是QPainter对象的绘制多边形的函数, 在这里我们暂且跳过, 我们需要关注的 是 getFillPolygon, 从名字上可以看出, 这个函数返回一个将要被填充的多边形范围

那么我们进入到 getFillPolygon 函数看看

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
        return QPolygonF();
      QPolygonF result(segment.size()+2);
      result[0] = getFillBasePoint(lineData->at(segment.begin()));
      std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
      result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
      result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
      return result;
}

getFillPolygon 函数, 通过传递进来的折线图的顶点列表, 再结合 getFillBasePoint 函数获取基线坐标(基线坐标指的是 X轴为水平坐标轴的情况下,  坐标轴 Y轴为0, X轴最左边和X轴最右边, 获取到的2个坐标). 这也就解释了为什么图形填充为什么总是填充到0

那么我们需要动的地方, 就在这里了, 有折线图的顶点坐标列表, 我们就能根据自己的需求, 实现一个我们想要的填充多边形

为了验证我们的猜想, 这里先进行一下测试, 直接返回一个固定形状, 看下是否会绘制出来

注意, 需要把 之前设置的图层间填充的代码屏蔽掉

// customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));

修改getFillPolygon函数如下

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    //        if (segment.size() < 2)
//            return QPolygonF();
//        QPolygonF result(segment.size()+2);
//        result[0] = getFillBasePoint(lineData->at(segment.begin()));


//        std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);


//        result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
//        result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
//        return result;

        //这里我们直接返回一个三角形
        QPolygonF result;
        result.append(QPointF(150, 50));
        result.append(QPointF(50, 150));
        result.append(QPointF(250, 150));
        return result;


}

然后编译运行, 结果如下所示

 可以看到, 和我们预想的一致, 确实绘制出来了一个三角形.

既然这样, 那我们完全可以按照顶点数据, 重新组组装出来一个多边形结构, 让它将超出我们设置的阈值的数据部分进行填充, 低于该值的则不填充.  

理论可行, 接下来进行修改

再次回到源码部分, 

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
        return QPolygonF();
      QPolygonF result(segment.size()+2);
      result[0] = getFillBasePoint(lineData->at(segment.begin()));
      
      //这里把顶点坐标复制到了多边形顶点坐标中, 这里我们做点处理
      std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
      
result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
      result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
      return result;
}

代码中的std::copy那一句, 把折线图的顶点坐标复制到了result容器中, result容器,正是保存填充色的容器, 我们就在复制这一步, 做点东西.

既然是超出阈值的才进行染色, 那么不难想到, 我们把数据低于阈值的, 全部设置成和阈值相等,  然后把基线也提升到和阈值相等,  是不是就行了? 

事实上,  如果只这么操作的话,  是会有点问题的, 先看下效果

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
            return QPolygonF();
        QPolygonF result(segment.size()+2);
//        result[0] = getFillBasePoint(lineData->at(segment.begin()));


//        std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);

        //计算出染色基准线, 这里把阈值设置为20, 也就是说, Y轴数据超出20的部分进行染色
        double divding = 20;
        QPointF start((*lineData)[0].x(), mValueAxis->coordToPixel(divding));
        QPointF end((*lineData).last().x(), mValueAxis->coordToPixel(divding));

        //基线起点
        result[0] = start;

        double divdingPix = mValueAxis->coordToPixel(divding);
        for(int i=0; i<lineData->size(); i++)
        {
            if((*lineData)[i].y() <= divdingPix)            //注意, lineData里边保存的是折线图的图像坐标, 左上角是0,0, 而不是我们图形中的左下角是 0,0
            {
                result.push_back((*lineData)[i]);
            }
            else
            {
                //所有低于阈值的, 都设置成阈值
                result.push_back(QPointF((*lineData)[i].x(), divdingPix));
            }
        }

        //基线终点
        result[result.size()-1] = end;



//        result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
//        result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
        return result;
}

将基线位置, 和多边形生成逻辑进行修改, 最终效果如下图

可以看到, 低于阈值的数据确实没了,  但是图好像塌下来了, 其实这个也好理解, 我们把低于阈值的数据全部设置的和阈值相等,  所以只有Y轴被提上去了,  但是折线从阈值线上跨切过去的点并没有被添加到多边形顶点坐标中

所以在这之前, 还需要有一步, 就是把X轴的位置找出来, 也就是从阈值线上跨切过去的坐标

分析出来了原因, 那就继续往下走,  把这些跨切坐标找到, 并添加到多边形顶点坐标中

修改之后的代码如下所示

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    //闭合区间第一个点: QPointF(40.2451,542)

            //使用交点检测方式插入数据
            if (segment.size() < 1)
                return QPolygonF();
            QPolygonF result(1);

            //计算出染色基准线
            double  divding = 20;     
            QPointF start((*lineData)[0].x(), mValueAxis->coordToPixel(divding));
            QPointF end((*lineData).last().x(), mValueAxis->coordToPixel(divding));
                   

            //闭合区间第一个点
            result[0] = QPointF(start.x(), mValueAxis->coordToPixel(divding));

            //阈值线
            QLineF line1(start, end);
            QPointF po;
            int pointCount = 0;

            //生成一个带有跨切点的顶点坐标列表
            QVector<QPointF> out;
            for(int i=0; i<lineData->size()-1; i++)
            {
                //使用QPointF类的判断线段交点的函数寻找交点
                QLineF line2((*lineData)[i], (*lineData)[i+1]);

                out.push_back((*lineData)[i]);

                if(line2.intersects(line1, &po))
                {
                    out.push_back(po);
                    ++pointCount;
                }
            }
            out.push_back(lineData->last());          //把缺失的最后一个点补上


            //这里遍历带有跨切点的像素坐标列表, 同时对数据大于限定值的数据进行限制
            for(int i=0; i<out.size(); i++)
            {
                //这里比较的是像素, 因此坐标轴上小的值,在这里反而会比较大
                if(out[i].y() > start.y())
                {
                    //如果数据值小于阈值线, 那就设置其和阈值线相等
                    result.push_back(QPointF(out[i].x(), start.y()));
                }
                else
                {
                    result.push_back(out[i]);
                }

            }
                
            //打印一下顶点数量和交点数量
            qDebug() << u8"总的点数:" << result.size() << u8"交点数量:" << pointCount;


            //闭合区间最后一个点
            result.push_back(end);
            return result;
}

上边的代码片中, 使用QPointF类的 intersects 函数, 找到了线段的交点, 然后将这个交点也加入到临时顶点坐标中. 然后对临时顶点坐标进行遍历, 并将小于阈值的数据修正到和阈值相等.

进行完了这一步之后,  输出就比较接近我们的需求了

调试输出打印:

总的点数: 19 交点数量: 7

 不考虑调试输出的话, 效果似乎还不错, 但是, 调试输出显示, 顶点数量有19个, 但我们的折线图中, 其实是没这么多顶点的,  而导致顶点数增加的原因, 就在于小于阈值的点, 我们把它移动到了阈值的Y轴位置,  但这一步其实可以省略. 因为有了最左侧和最右侧的蓝色圈位置的顶点, 就已经可以决定填充色多边形的走向了. 因此, 可以把上边代码中的小于阈值部分, 移动到阈值部分的代码注释掉, 直接丢弃这个顶点坐标

//这里比较的是像素, 因此坐标轴上小的值,在这里反而会比较大
if(out[i].y() > start.y())
{
     //数据值小于阈值线, 那就完全可以丢弃了, 这里就不往顶点数据里边加了, 直接注释掉
     //result.push_back(QPointF(out[i].x(), start.y()));
}
else
{
    result.push_back(out[i]);
}

再次运行一下代码,  显示的结果一样, 但是顶点数量就少了

调试输出打印

总的点数: 14 交点数量: 7

可以看到, 相比原来的19个顶点, 现在变成了14个顶点,  这少掉的5个顶点, 其实就是小于阈值的点

 这5个顶点, 从填充多边形的顶点中删除掉了, 但是由于跨切交点的存在, 所以对图形展现并没有影响, 等到数据量多起来的时候, 这一操作可以有效的省略掉大量的点.

比如我们把点数增加到100看看

总的点数: 168 交点数量: 66
总的点数: 118 交点数量: 66

仅仅100个数据点的情况下, 就少掉了50个点, 如果每一条线的点数达到几十K的时候, 这些点数对速度和内存的影响就会大起来.

写到这里, 后边其实也就没什么好说的了

把图形竖起来的话,  只需要把X轴设置成Y轴, Y轴设置成X轴就行了

customPlot->graph(0)->setKeyAxis(ui->customPlot->yAxis);
customPlot->graph(0)->setValueAxis(ui->customPlot->xAxis);

然后再加个阈值变量, 基本上就完事了

6: 效果展示

最后, 在前边的代码基础上, 添加几个图层, 加一些数据看下效果

 

 

 

 改个颜色, 就得到了标题图

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zh15237774494/article/details/118753006

智能推荐

GB∕T 33171-2016 城市交通运行状况评价规范_城市交通运行状况评价规范 下载-程序员宅基地

文章浏览阅读407次,点赞5次,收藏8次。标准号:GB/T 33171-2016中文标准名称:城市交通运行状况评价规范 英文标准名称:Specification for urban traffic performance evaluation_城市交通运行状况评价规范 下载

ROS1与ROS2的bag包互换(包含自定义消息)_ros2的rosbag可以用ros1播放么-程序员宅基地

文章浏览阅读7.3k次,点赞8次,收藏48次。https://blog.csdn.net/shanpenghui/article/details/117282535https://blog.csdn.net/weixin_37532614/article/details/109602947https://blog.csdn.net/weixin_41010198/article/details/117042386_ros2的rosbag可以用ros1播放么

Python报错:RuntimeError: one of the variables needed for gradient computation has been modified by_python runtimeerror: one of the variables needed f-程序员宅基地

文章浏览阅读978次。Python报错:RuntimeError: one of the variables needed for gradient computation has been modified by_python runtimeerror: one of the variables needed for gradient computation ha

使用协同过滤方法来改善推荐系统的效果(Using collaborative filtering method-程序员宅基地

文章浏览阅读372次。作者:禅与计算机程序设计艺术 1.简介推荐系统是互联网领域的一个重要组成部分。随着互联网用户的增长,网站和应用都需要提供更加个性化的服务,提高用户的黏性。推荐系统帮助用户发现感兴趣的内容、产品或服务,并根据个人喜好进行排序。比如,当用户在电子商务网站上浏览商品时,推荐系统会推荐相关的商品给用户,这些推

递归法:一个实数数列共有N项,已知ai=(ai-1 - ai+1) / 2 + d_已知ai,ai-1,如何求通项公式-程序员宅基地

文章浏览阅读2.5k次。题目描述:一个实数数列共有N项,已知ai = (a_i-1 - a_i+1) / 2 + d, (1 < i < N)(N < 60)键盘输入N,d, a1, an, n, 输出 an。进行简单的代换2ai - 2d = ai-1 - ai+1 ==>ai+1 = ai-1 - 2ai + 2d ==>ai = ai-2 - 2ai-1 + 2d 2 &..._已知ai,ai-1,如何求通项公式

【机器学习小常识】“parameters(参数)” 与 “hyperparameters(超参数)”的概念及区别详解_parameters参数-程序员宅基地

文章浏览阅读8.1k次,点赞8次,收藏32次。目录1、参数与超参数概念1.什么是参数2.什么是超参数2、二者有什么区别1.得到方式不同2.影响因素不同3.可控程度不同说在前面的话有些概念其实,很纠结,它到底是属于机器学习呢,还是属于深度学习呢?比如说,分类与回归,这个有些是很难界定的,有人说,它属于深度学习,又有人说,它属于机器学习。其实争论这些,好像并无太大意义,就像一个男生对一个姑娘宠溺的说:这天下..._parameters参数

随便推点

打表法-程序员宅基地

文章浏览阅读4.8k次,点赞9次,收藏24次。今天见到了传说中的打表法,有人说这是流氓算法,但是我觉得这个也是非常牛逼的。下面就来说说这个打表法把,打表法对于某些用时较长的题目非常的有用。就是将我们要的结果打印到一个文本文档中,然后直接调用这个结果就可以了。在编译的时候就不用再程序里面计算,这样就省了很多时间,。是不是非常的牛逼呢,哈哈程序如下;#include<iostream>#include<..._打表法

Ribbon实战与原理剖析_ribbon原理-程序员宅基地

文章浏览阅读2.3k次,点赞2次,收藏11次。通过实现IRule接口可以自定义负载策略,主要的选择服务逻辑在 choose 方法中。}_ribbon原理

Centos 安装GPU并行lammps_centos lammps gpu-程序员宅基地

文章浏览阅读6.1k次,点赞6次,收藏16次。以下描述了如何在Centos服务器上安装GPU版本的lammps。安装环境目标属性系统Linux/CentOS 7.6CPU12*Intel Xeon CPU E5-2609 v3 @ 1.90GHzGPU2*NVIDIA Tesla K80/CUDA 8.0安装准备1. CUDA由于是安装GPU版本lammps,首先应确保系统安装有显卡所对..._centos lammps gpu

小米HR:说说对API有多少的理解? 看了后,和面试官扯皮,吹牛逼!绰绰有余!_能直接温hr是不是刷api的吗-程序员宅基地

文章浏览阅读2.9k次,点赞4次,收藏32次。目录什么是API?什么是API测试API测试的测试用例:API测试方法:如何进行API测试API测试的最佳做法:API测试检测到的错误类型API测试工具API测试的挑战结论:最后什么是API?API(全称Application Programming Interface)是两个单独的软件系统之间的通信和数据交换。实现API的软件系统包含可以由另一个软件系统执行的功能/子例程。什么是API测试API测试是一种用于验证API(应用程序编程接口)的._能直接温hr是不是刷api的吗

Android开发 入门篇(二) - 常用UI控件_能(textview、edittext、button、progressbar、alertdialog-程序员宅基地

文章浏览阅读915次,点赞2次,收藏7次。date: 2020-01-12 21:46:05文章目录控件ButtonTextViewEditTextImageViewProgressBarAlertDialogProgressDialog布局LenearLayoutandroid:layout_gravityandroid:layout_weightRelativeLayoutFrameLayout百分比布局其他自定义控件ListVie..._能(textview、edittext、button、progressbar、alertdialog、progressdialog、lis

python3.4中文版下载-Python 3.4 入门指南 官方中文版.pdf-程序员宅基地

文章浏览阅读1.2k次。【实例简介】【实例截图】【核心代码】CONTENTS1 开胃菜 22 使用 Python 解释器 42.1 调用 Python 解释器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42.2 解释器及其环境 . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...._python_3.4_入门指南(官方中文版).pdf

推荐文章

热门文章

相关标签