diff --git a/.gitignore b/.gitignore index 9a1dad58..cac5fe0d 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,9 @@ backend/build applications/build applications/applications -*html + *Doxyfile -*latex \ No newline at end of file +*/html/ +*/latex/ + +*.vscode diff --git a/EDP/InputWidgetEDP.cpp b/EDP/InputWidgetEDP.cpp index 9abab64e..bb8afd93 100644 --- a/EDP/InputWidgetEDP.cpp +++ b/EDP/InputWidgetEDP.cpp @@ -87,7 +87,7 @@ InputWidgetEDP::makeEDP(void) QPushButton *removeEDP = new QPushButton(); removeEDP->setMinimumWidth(75); removeEDP->setMaximumWidth(75); - removeEDP->setText(tr("Clear all")); + removeEDP->setText(tr("Clear All")); connect(removeEDP,SIGNAL(clicked()),this,SLOT(removeEDP())); titleLayout->addWidget(title); diff --git a/Examples/Examples.json b/Examples/Examples.json index d9f01d07..4e981c0e 100644 --- a/Examples/Examples.json +++ b/Examples/Examples.json @@ -11,12 +11,12 @@ "inputFile": "qfem-0002/src/input.json" }, { - "name": "Basic modeling with Python", + "name": "Basic Modeling with Python", "description": "This example illustrates how Python scripting can be used with quoFEM to express general mathematical models without the use of a dedicated finite element analysis engine.", "inputFile": "qfem-0005/src/input.json" }, { - "name": "Optimization", + "name": "Deterministic Calibration", "description": "In this example, a **parameter estimation** routine is used to solve a classical optimization problem for which an analytic solution is known.", "inputFile": "qfem-0006/src/input.json" }, diff --git a/Examples/qfem-0001/README.rst b/Examples/qfem-0001/README.rst index b11504ad..69abf982 100644 --- a/Examples/qfem-0001/README.rst +++ b/Examples/qfem-0001/README.rst @@ -18,29 +18,33 @@ The structure has uncertain properties that all follow normal distribution: The goal of the exercise is to estimate the mean and standard deviation of the vertical displacement at node 3. The exercise requires two files. The user is required to download these files and place them in a **NEW** folder. The two files are: -1. :qfem-0001:`TrussTemplate.tcl ` +.. dropdown:: 1. Main file: :qfem-0001:`TrussTemplate.tcl ` -.. literalinclude:: ../qfem-0001/src/TrussModel.tcl - :language: tcl + .. literalinclude:: ../qfem-0001/src/TrussModel.tcl + :language: tcl .. note:: The first lines containing ``pset`` will be read by the application when the file is selected and the application will autopopulate the Random Variables input panel with these same variable names. It is of course possible to explicitly use Random Variables without the ``pset`` command as is demonstrated in the verification section. -2. :qfem-0001:`TrussPost.tcl <../qfem-0001/src/TrussPost.tcl>`. - The ``TrussPost.tcl`` script shown below will accept as input any of the 6 nodes in the domain and for each of the two dof directions. +.. dropdown:: 2. Postprocessing file: :qfem-0001:`TrussPost.tcl <../qfem-0001/src/TrussPost.tcl>`. + + .. literalinclude:: ../qfem-0001/src/TrussPost.tcl + :language: tcl -.. literalinclude:: ../qfem-0001/src/TrussPost.tcl - :language: tcl .. note:: - The use has the option to provide no post-process script (in which case the main script must create a ``results.out`` file containing a single line with as many space separated numbers as QoI or the user may provide a Python script that also performs the postprocessing. An example of a postprocessing Python script is :qfem-0001:`TrussPost.py `. + The use has the option to provide no post-process script (in which case the main script must create a ``results.out`` file containing a single line with as many space separated numbers as QoI or the user may provide a Python script that also performs the postprocessing. Below is an example of a postprocessing Python script. + + .. dropdown:: Alternative postprocessing file: :qfem-0001:`TrussPost.py ` + + .. literalinclude:: ../qfem-0001/src/TrussPost.py + :language: python + - .. literalinclude:: ../qfem-0001/src/TrussPost.py - :language: python .. warning:: @@ -138,9 +142,6 @@ Global Sensitivity In a global sensitivity analysis the user is wishing to understand what is the influence of the individual random variables on the quantities of interest. This is typically done before the user launches large scale forward uncertainty problems in order to limit the number of random variables used so as to limit the number of simulations performed. -To perform a reliability analysis the steps above would be repeated with the exception that the user would select a reliability analysis method instead of a Forward Propagation method. To obtain reliability results using the Second-Order Reliability Method (SORM) for the truss problem the user would follow the same sequence of steps as previously. The difference would be in the **UQ** tab in which the user would select a Reliability as the Dakota Method Category and then choose Local reliability. In the figure the user is specifying that they are interested in the probability that the displacement will exceed certain response levels. - - .. figure:: figures/trussSens-UQ.png :align: center :figclass: align-center diff --git a/Examples/qfem-0002/README.md b/Examples/qfem-0002/README.md index fac4e194..d13dfa1b 100644 --- a/Examples/qfem-0002/README.md +++ b/Examples/qfem-0002/README.md @@ -45,10 +45,9 @@ To define the uncertainty workflow in quoFEM, select **Forward Propagation** for The following files make up the **FEM** model definition. -#. [model.py](https://raw.githubusercontent.com/claudioperez/SimCenterExamples/master/static/truss/model.py): This file is a Python script which takes a given realization of the problem's random variables, and runs a finite element analysis of the truss with OpenSeesPy. It is supplied to the **Input Script** field of the **FEM** tab, and obviates the need for supplying a **Postprocess Script**. When this script is invoked in the workflow, it receives the list of the identifiers supplied in the **QoI** tab through the operating system's `stdout` variable, and a set of random variable realizations by star-importing the **Parameters File** from the **FEM** tab. - -#. [params.py](https://raw.githubusercontent.com/claudioperez/SimCenterExamples/master/static/truss/params.py): This file is a Python script which defines the problem's random variables as objects in the Python runtime. It is supplied to the **Parameters File** field of the **FEM** tab. *The literal values which are assigned to variables in this file will be varied at runtime by the UQ engine.* +#. [TrussModel.py](https://github.com/NHERI-SimCenter/quoFEM/tree/master/Examples/qfem-0002/src/TrussModel.py): This file is a Python script which takes a given realization of the problem's random variables, and runs a finite element analysis of the truss with OpenSeesPy. It is supplied to the **Input Script** field of the **FEM** tab, and obviates the need for supplying a **Postprocess Script**. When this script is invoked in the workflow, it receives the list of the identifiers supplied in the **QoI** tab through the operating system's `stdout` variable, and a set of random variable realizations by star-importing the **Parameters File** from the **FEM** tab. +#. [TrussParams.py](https://github.com/NHERI-SimCenter/quoFEM/tree/master/Examples/qfem-0002/src/TrussParams.py): This file is a Python script which defines the problem's random variables as objects in the Python runtime. It is supplied to the **Parameters File** field of the **FEM** tab. *The literal values which are assigned to variables in this file will be varied at runtime by the UQ engine.* diff --git a/Examples/qfem-0006/src/input.json b/Examples/qfem-0006/src/input.json index a877f294..905c56c9 100644 --- a/Examples/qfem-0006/src/input.json +++ b/Examples/qfem-0006/src/input.json @@ -12,24 +12,26 @@ }, "randomVariables": [ { + "distribution": "ContinuousDesign", + "initialpoint": 0, + "inputType": "Parameters", + "lowerbound": -2, "name": "X", - "title": "first variable", - "distribution": "Uniform", - "lowerbound": -2.0, "refCount": 1, - "upperbound": 2.0, + "upperbound": 2, "value": "RV.X", - "variableClass": "Uncertain" + "variableClass": "Design" }, { + "distribution": "ContinuousDesign", + "initialpoint": 0, + "inputType": "Parameters", + "lowerbound": -2, "name": "Y", - "title": "second variable", - "distribution": "Uniform", - "lowerbound": -2.0, "refCount": 1, - "upperbound": 2.0, + "upperbound": 2, "value": "RV.Y", - "variableClass": "Uncertain" + "variableClass": "Design" } ], "FEM":{ diff --git a/Examples/qfem-0014/src/input.json b/Examples/qfem-0014/src/input.json index 2119241f..ece52e90 100644 --- a/Examples/qfem-0014/src/input.json +++ b/Examples/qfem-0014/src/input.json @@ -30,7 +30,7 @@ "calDataFilePath": "{Current_Dir}/.", "logLikelihoodFile": "", "logLikelihoodPath": "", - "numParticles": 10, + "numParticles": 200, "seed": 0, "uqEngine": "UCSD-UQ", "uqType": "Transitional Markov chain Monte Carlo" diff --git a/Examples/qfem-0015/README.rst b/Examples/qfem-0015/README.rst index ca2a9842..404d3d41 100644 --- a/Examples/qfem-0015/README.rst +++ b/Examples/qfem-0015/README.rst @@ -67,7 +67,7 @@ Once the user selects OpenSeesPy as FEM applications, below three fields are req UQ Workflow ------------- -1. Start the application, and the **UQ** Selection will be highlighted. Change the UQ Engine to **SimCenterUQ** and the Method Category to **Training GP Surrogate Model**. Since the model is provided, the Training Dataset will be obtained by **Sampling and Simulation**. +1. Start the application, and the **UQ** Selection will be highlighted. Change the UQ Engine to **SimCenterUQ** and the Method Category to **Training GP Surrogate Model**. Since the model is provided, the Training Dataset will be obtained by **Sampling and Simulation**. Default settings are used for the advanced options. .. figure:: figures/SUR-UQtab1.png :align: center @@ -144,6 +144,8 @@ Well-trained model will form a clear diagonal line while poorly trained model ar :figclass: align-center :width: 1200 +.. note:: + Note that in the second training period, 150 initial samples were provided from the data files and 150 more simulations were conducted. However, the number of total samples used to train the surrogate model is displayed as 299 since one simulation is consumed to check the consistency between the user provided model (in the **FEM tab**) and the dataset (in the **UQ tab**). * Leave-one-out cross-validation (LOOCV) predictions: @@ -154,8 +156,6 @@ Well-trained model will form a clear diagonal line while poorly trained model ar Users may want to perform additional simulations in a similar way. -.. note:: - Note that in the second training period, 150 initial samples were provided from the data files and 150 more simulations were conducted. However, the number of total samples used to train the surrogate model is displayed as 299 since one simulation is consumed to check the consistency between the user provided model (in the **FEM tab**) and the dataset (in the **UQ tab**). @@ -174,6 +174,10 @@ Once surrogate model is constructed, it can be used for various UQ/optimization .. note:: * Do not change the name of ``templatedir_SIM``. **SurrogateGP Info and model** file names may be changed. * When location of the files are changed, ``templatedir_SIM`` should be always located in the directory same to the **SurroateGP Info file**. + +.. warning:: + + Do not place above surrogate model files in your root, downloads, or desktop folder as when the application runs it will copy the contents on the directories and subdirectories containing these files multiple times. If you are like us, your root, Downloads or Documents folders contains and awful lot of files and when the backend workflow runs you will slowly find you will run out of disk space! 2. Restart the quoFEM (or press **UQ tab**) and select Dakota Forward Propagation method. diff --git a/Examples/qfem-0016/README.rst b/Examples/qfem-0016/README.rst index ece65c8c..3378ef14 100644 --- a/Examples/qfem-0016/README.rst +++ b/Examples/qfem-0016/README.rst @@ -16,7 +16,7 @@ This example constructs a Gaussian process-based surrogate model for mean and st Problem description ------------------- -The structure (:qfem-0016:`three story nonlinear building stick model `) has the following uncertain properties: +The structure ( :ref:`three story nonlinear building stick model `_ ) has the following uncertain properties: ============================= ============ ========= Random Variable lower bound upper bound @@ -120,6 +120,9 @@ Once the surrogate model is trained, it can be used for various UQ/optimization * Do not change the name of ``templatedir_SIM``. **SurrogateGP Info and model** file names may be changed. * When location of the files are changed, ``templatedir_SIM`` should be always located in the directory same to the **SurroateGP Info file**. +.. warning:: + Do not place above surrogate model files in your root, downloads, or desktop folder as when the application runs it will copy the contents on the directories and subdirectories containing these files multiple times. If you are like us, your root, Downloads or Documents folders contains and awful lot of files and when the backend workflow runs you will slowly find you will run out of disk space! + 2. Restart the quoFEM (or press **UQ tab**) and select Dakota sensitivity analysis method. .. figure:: figures/SUR2-VER1.png diff --git a/Examples/qfem-0019/src/input.json b/Examples/qfem-0019/src/input.json index b8af39f1..fa35ca1c 100644 --- a/Examples/qfem-0019/src/input.json +++ b/Examples/qfem-0019/src/input.json @@ -25,7 +25,7 @@ "calDataFilePath": "{Current_Dir}/.", "logLikelihoodFile": "", "logLikelihoodPath": "", - "numParticles": 10, + "numParticles": 200, "seed": 0, "uqEngine": "UCSD-UQ", "uqType": "Transitional Markov chain Monte Carlo" diff --git a/FEM/CustomFEM.cpp b/FEM/CustomFEM.cpp new file mode 100644 index 00000000..163bd89b --- /dev/null +++ b/FEM/CustomFEM.cpp @@ -0,0 +1,399 @@ +/* ***************************************************************************** +Copyright (c) 2016-2017, The Regents of the University of California (Regents). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS +PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, +UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +*************************************************************************** */ + +// Written: fmckenna + +#include "CustomFEM.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//using namespace std; +#include + +#include +#include + +//#include + +CustomFEM::CustomFEM(QWidget *parent) + : SimCenterAppWidget(parent) +{ + + theCustomInputNumber = new QSpinBox(); + theCustomLayout = new QVBoxLayout(); + theCustomFileInputs = new QVBoxLayout(); + theCustomFileInputWidget = new QWidget(); + + QLabel *customNumberLabel = new QLabel(); + customNumberLabel->setText(tr("Number of input files and executables required by driver script")); + QHBoxLayout *numberLayout = new QHBoxLayout(); + numberLayout->addWidget(customNumberLabel); + numberLayout->addWidget(theCustomInputNumber); + numberLayout->addStretch(); + + theCustomLayout->addLayout(numberLayout); + + theCustomFileInputs = new QVBoxLayout(); + theCustomFileInputWidget = new QWidget(); + theCustomFileInputWidget->setLayout(theCustomFileInputs); + theCustomLayout->addWidget(theCustomFileInputWidget); + + QVBoxLayout *customLayout = new QVBoxLayout(); + + // Add location to add driver script + QLabel *textCustom=new QLabel(); + textCustom->setStyleSheet("font-weight: bold"); + + textCustom->setText(tr("Input driver script that runs and post-processes application")); + QSpacerItem *customSpacer = new QSpacerItem(50,10); + + QLabel *driverLabel = new QLabel(); + driverLabel->setText(tr("Driver script")); + driverFile = new QLineEdit; + QPushButton *chooseDriver = new QPushButton(); + chooseDriver->setText(tr("Choose")); + connect(chooseDriver, &QPushButton::clicked, this, [=](){ + QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"),"", "All files (*)"); + if (!fileName.isEmpty()) { + driverFile->setText(fileName); + } + }); + chooseDriver->setToolTip(tr("Push to choose a file from your file system")); + + QGridLayout *driverLayout = new QGridLayout(); + driverLayout->addWidget(driverLabel, 1, 0); + driverLayout->addWidget(driverFile, 1, 1); + driverLayout->addWidget(chooseDriver, 1, 2); + driverLayout->setColumnStretch(3,1); + driverLayout->setColumnStretch(1,3); + + // Add location to add post-processing script + QLabel *postProcessingLabel = new QLabel(); + postProcessingLabel->setText("Postprocessing script"); + QLineEdit *postProcessScript = new QLineEdit; + QPushButton *choosePostProcessing = new QPushButton(); + + connect(choosePostProcessing, &QPushButton::clicked, this, [=](){ + QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"),"", "All files (*)"); + if (!fileName.isEmpty()) { + postProcessScript->setText(fileName); + } + }); + choosePostProcessing->setText(tr("Choose")); + choosePostProcessing->setToolTip(tr("Push to choose a file from your file system")); + + QGridLayout *postProcessLayout = new QGridLayout(); + postProcessLayout->addWidget(postProcessingLabel, 1, 0); + postProcessLayout->addWidget(postProcessScript, 1, 1); + postProcessLayout->addWidget(choosePostProcessing, 1, 2); + postProcessLayout->setColumnStretch(3,1); + postProcessLayout->setColumnStretch(1,3); + + // Add custom layout for specifying additional inputs + customLayout->addWidget(textCustom); + customLayout->addItem(customSpacer); + customLayout->addLayout(driverLayout); + customLayout->addLayout(postProcessLayout); + customLayout->addLayout(theCustomLayout); + //customLayout->addStretch(); + + connect(theCustomInputNumber, SIGNAL(valueChanged(int)), this, SLOT(customInputNumberChanged(int))); + theCustomInputNumber->setValue(0); + + this->setLayout(customLayout); + +} + +CustomFEM::~CustomFEM() +{ + +} + + +void CustomFEM::clear(void) +{ + QStringList names; + for (int i=0; iremoveRandomVariables(names); + //postprocessScript->setText(""); + //driverFile->setText(""); +} + +bool +CustomFEM::inputFromJSON(QJsonObject &jsonObject) +{ +// varNamesAndValues.clear(); + +// if (jsonObject.contains("randomVariables")) { +// QJsonArray randomVars = jsonObject["randomVariables"].toArray(); +// foreach (const QJsonValue & value, randomVars) { +// QJsonObject theRV = value.toObject(); +// QString name = theRV["name"].toString(); +// QString zero = "0"; +// varNamesAndValues.append(name); +// varNamesAndValues.append(zero); +// } +// } + + return true; +} + +bool +CustomFEM::outputToJSON(QJsonObject &jsonObject) { + + // Add driver script and post-processing script to JSON file + + return true; +} + + +bool +CustomFEM::outputAppDataToJSON(QJsonObject &jsonObject) { + + + bool result = true; + // + // per API, need to add name of application to be called in AppLication + // and all data to be used in ApplicationDate + // + + jsonObject["Application"] = "customFEM"; + QJsonObject dataObj; + + QString driverScript = driverFile->text(); + QString postProcessScript = postprocessScript->text(); + dataObj["inputFile"] = driverScript; + dataObj["postprocessScript"] = postProcessScript; + + QFileInfo driverInfo(driverScript); + + dataObj["mainInput"] = driverInfo.fileName(); + QString path = driverInfo.absolutePath(); + dataObj["dir"] = path; + + QFileInfo postProcessInfo(postProcessScript); + dataObj["mainPostprocessScript"] = postProcessInfo.fileName(); + + // Add all additional input files to JSON + QJsonArray apps; + for (auto const val : customInputFiles) { + apps.append(val->text()); + } + dataObj["fileInfo"] = apps; + + + jsonObject["ApplicationData"] = dataObj; + + return true; // needed for json file to save, copyFiles will return false +} + + bool +CustomFEM::inputAppDataFromJSON(QJsonObject &jsonObject) { + + // + // from ApplicationData + // + + if (jsonObject.contains("ApplicationData")) { + QJsonObject dataObject = jsonObject["ApplicationData"].toObject(); + + // + // retrieve filename and path, set the QLIne Edit + // + + int count = 0; + auto fileListArray = dataObject["fileInfo"].toArray(); + auto numFiles = fileListArray.count(); + theCustomInputNumber->setValue(numFiles); + customInputFiles.resize(numFiles); + for (auto const& val : fileListArray) { + auto b = val.toString(); + //auto inputFile = new QLineEdit(b); + //customInputFiles.append(inputFile); + customInputFiles.at(count)->setText(b); + count++; + } + } + + return true; + } + + +void +CustomFEM::setMainScript(QString name1){ + + return; +} + +void +CustomFEM::chooseMainScript(void) { + QString fileName=QFileDialog::getOpenFileName(this,tr("Open File"), + "", + "All files (*.tcl)"); + if(!fileName.isEmpty()) { + this->setMainScript(fileName); + } +} + +void +CustomFEM::specialCopyMainScript(QString fileName, QStringList varNames) { + // pass +} + +bool +CustomFEM::copyFiles(QString &dirName) { + + QString fileName = driverFile->text(); + + if (fileName.isEmpty()) { + this->errorMessage("Custom - no file set"); + return false; + } + QFileInfo fileInfo(fileName); + + QString theFile = fileInfo.fileName(); + QString thePath = fileInfo.path(); + + SimCenterAppWidget::copyPath(thePath, dirName, false); + + RandomVariablesContainer *theRVs=RandomVariablesContainer::getInstance(); + QStringList varNames = theRVs->getRandomVariableNames(); + + // now create special copy of original main script that handles the RV + OpenSeesParser theParser; + QString copiedFile = dirName + QDir::separator() + theFile; + theParser.writeFile(fileName, copiedFile, varNames); + + return true; +} + + +void CustomFEM::customInputNumberChanged(int numCustomInputs) { + // numInputs = numCustomInputs; + auto tempLayout = theCustomFileInputs; + + // Delete old inputs + QLayoutItem * item; + while ((item = tempLayout->takeAt(0)) != nullptr) { + if (item->layout() != nullptr) { + auto sublayout = item->layout(); + while (sublayout->takeAt(0) != nullptr) { + if (sublayout->takeAt(0)->widget() != nullptr) { + auto widget = sublayout->takeAt(0)->widget(); + sublayout->removeWidget(widget); + widget->deleteLater(); + } + } + tempLayout->removeItem(item); + item->layout()->deleteLater(); + } else if (item->widget() != nullptr) { + tempLayout->removeItem(item); + item->widget()->deleteLater(); + } + } + + tempLayout->deleteLater(); + tempLayout = nullptr; + + + // Create new file inputs + QVBoxLayout * newLayout = new QVBoxLayout(); + customInputFiles.clear(); + for (int i = 0; i < numCustomInputs; ++i) { + auto inputLabel = new QLabel(); + auto inputFile = new QLineEdit(); + inputLabel->setText(tr("Input location")); + auto *chooseFile = new QPushButton(); + chooseFile->setText(tr("Choose")); + connect(chooseFile, &QPushButton::clicked, this, [=](){ + QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"),"", "All files (*)"); + if (!fileName.isEmpty()) { + inputFile->setText(fileName); + } + }); + chooseFile->setToolTip(tr("Push to choose a file from your file system")); + + auto inputLayout = new QHBoxLayout(); + inputLayout->addWidget(inputLabel); + inputLayout->addWidget(inputFile); + inputLayout->addWidget(chooseFile); + newLayout->addLayout(inputLayout); + customInputFiles.append(inputFile); + } + //newLayout->addStretch(); + + // Replace outdated widget with new one containing new file input layout + theCustomFileInputs = newLayout; + theCustomFileInputWidget->layout()->deleteLater(); + + theCustomLayout->removeWidget(theCustomFileInputWidget); + theCustomFileInputWidget->deleteLater(); + + theCustomFileInputWidget = new QWidget(); + theCustomFileInputWidget->setLayout(theCustomFileInputs); + theCustomLayout->addWidget(theCustomFileInputWidget); + + newLayout = nullptr; +} + + +QVector< QString > CustomFEM::getCustomInputs() const { + QVector< QString > stringOutput(customInputFiles.size()); + + unsigned int count = 0; + for (auto const& val : customInputFiles) + { + stringOutput[count] = val->text(); + count++; + } + + return stringOutput; +} diff --git a/FEM/CustomFEM.h b/FEM/CustomFEM.h new file mode 100644 index 00000000..46106205 --- /dev/null +++ b/FEM/CustomFEM.h @@ -0,0 +1,93 @@ +#ifndef CUSTOM_FEM_MODEL_H +#define CUSTOM_FEM_MODEL_H + +/* ***************************************************************************** +Copyright (c) 2016-2017, The Regents of the University of California (Regents). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS +PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, +UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +*************************************************************************** */ + +// Written: fmckenna + +#include + +#include +#include +#include +#include +#include + + +class InputWidgetParameters; +class RandomVariablesContainer; + +class CustomFEM : public SimCenterAppWidget +{ + Q_OBJECT +public: + explicit CustomFEM(QWidget *parent = 0); + ~CustomFEM(); + + bool outputToJSON(QJsonObject &rvObject) override; + bool inputFromJSON(QJsonObject &rvObject) override; + bool outputAppDataToJSON(QJsonObject &rvObject) override; + bool inputAppDataFromJSON(QJsonObject &rvObject) override; + bool copyFiles(QString &dirName) override; + + void setMainScript(QString filnema1); + +signals: + +public slots: + void clear(void); + void chooseMainScript(void); + void customInputNumberChanged(int numCustomInputs) ; + +private: + void specialCopyMainScript(QString fileName, QStringList varNamesAndValues); + QVector< QString > getCustomInputs() const; + + + QSpinBox *theCustomInputNumber; + QVBoxLayout *theCustomLayout; + QWidget *theCustomFileInputWidget; + QVBoxLayout *theCustomFileInputs; + + QVectorcustomInputFiles; + + QLineEdit *driverFile; + QLineEdit *postprocessScript; + QStringList varNamesAndValues; +}; + +#endif // CUSTOM_FEM_MODEL_H diff --git a/FEM/FEM_Selection.cpp b/FEM/FEM_Selection.cpp index 2003f003..2724c9a6 100644 --- a/FEM/FEM_Selection.cpp +++ b/FEM/FEM_Selection.cpp @@ -39,6 +39,7 @@ UPDATES, ENHANCEMENTS, OR MODIFICATIONS. #include #include #include +#include #include #include @@ -50,11 +51,15 @@ FEM_Selection::FEM_Selection(bool inclMulti, QWidget *parent) SimCenterAppWidget *openseesPy = new OpenSeesPyFEM(); SimCenterAppWidget *FEAPpv = new FEAPpvFEM(); SimCenterAppWidget *surrogateGp = new surrogateGpFEM(); + SimCenterAppWidget *customFEM = new CustomFEM(); this->addComponent(QString("OpenSees"), QString("OpenSees"), opensees); - this->addComponent(QString("OpenSeesPy"), QString("OpenSeesPy"), openseesPy); + this->addComponent(QString("Python"), QString("OpenSeesPy"), openseesPy); this->addComponent(QString("FEAPpv"), QString("FEAPpv"), FEAPpv); this->addComponent(QString("SurrogateGP"), QString("SurrogateGP"), surrogateGp); + //this->addComponent(QString("CustomFEM"), QString("CustomFEM"), customFEM); + this->addComponent(QString("None"), QString("None"), new SimCenterAppWidget()); + if (inclMulti == true) { SimCenterAppWidget *multi = new SimCenterAppMulti(QString("FEM"), QString("MultiModel-FEM"),this, this); this->addComponent(QString("Multi Model"), QString("MultiModel-FEM"), multi); diff --git a/backend/modules/performUQ/UCSD_UQ/errorMarginalizedLogLikeScript.py b/backend/modules/performUQ/UCSD_UQ/errorMarginalizedLogLikeScript.py new file mode 100644 index 00000000..af544ca8 --- /dev/null +++ b/backend/modules/performUQ/UCSD_UQ/errorMarginalizedLogLikeScript.py @@ -0,0 +1,77 @@ +import numpy as np + + +def log_likelihood(calibrationData, prediction, parameters, numExperiments, covarianceMatrixList, edpNamesList, + edpLengthsList, covarianceMultiplierList, scaleFactors, shiftFactors): + """ Compute the log-likelihood + + :param calibrationData: Calibration data consisting of the measured values of response. Each row contains the data + from one experiment. The length of each row equals the sum of the lengths of all response quantities. + :type calibrationData: numpy ndarray (atleast_2d) + + :param prediction: Prediction of the response from the model, evaluated using the parameter values for + which the log-likelihood function needs to be calculated. + :type prediction: numpy ndarray (atleast_2d) + + :param parameters: A sample value of the model parameter vector. + :type parameters: numpy ndarray + + :param numExperiments: Number of experiments from which data is available, this is equal to the number of rows + (i.e., the first index) of the calibration data array + :type numExperiments: int + + :param covarianceMatrixList: A list of length numExperiments * numResponses, where each item in the list contains + the covariance matrix or variance value corresponding to that experiment and response quantity, i.e., each item in + the list is a block on the diagonal of the error covariance matrix + :type covarianceMatrixList: list of numpy ndarrays + + :param edpNamesList: A list containing the names of the response quantities + :type edpNamesList: list of strings + + :param edpLengthsList: A list containing the length of each response quantity + :type edpLengthsList: list of ints + + :param covarianceMultiplierList: A list containing multipliers on the default covariance matrices or variance + values. The length of this list is equal to the number of response quantities. These additional variables, one + per response quantity, are parameters of the likelihood model whose values are also calibrated. + :type covarianceMultiplierList: list of floats + + :param scaleFactors: A list containing the normalizing factors used to scale (i.e. divide) the model + prediction values. The length of this list is equal to the number of response quantities. + :type scaleFactors: list of ints + + :param shiftFactors: A list containing the values used to shift (i.e. added to) the prediction values. The length of + this list is equal to the number of response quantities. + :type shiftFactors: list of ints + + :return: loglikelihood. This is a scalar value, which is equal to the logpdf of a zero-mean multivariate normal + distribution and a user-supplied covariance structure. Block-diagonal covariance structures are supported. The value + of multipliers on the covariance block corresponding to each response quantity is also calibrated. + :rtype: float + """ + numResponses = len(edpLengthsList) + # Shift and normalize the prediction + currentPosition = 0 + for j in range(numResponses): + prediction[:, currentPosition:currentPosition + edpLengthsList[j]] += shiftFactors[j] + prediction[:, currentPosition:currentPosition + edpLengthsList[j]] /= scaleFactors[j] + currentPosition += edpLengthsList[j] + + # Compute the normalized residuals + allResiduals = prediction - calibrationData + + # Loop over the normalized residuals to compute the log-likelihood + loglike = 0 + for i in range(numExperiments): + currentPosition = 0 + for j in range(numResponses): + # Get the residuals corresponding to this response variable + length = edpLengthsList[j] + residuals = allResiduals[i, currentPosition:currentPosition + length] + currentPosition += length + ll = -length * np.log(np.sum(residuals ** 2)) + if not np.isnan(ll): + loglike += ll + else: + loglike += -np.inf + return loglike diff --git a/backend/modules/performUQ/other/HeirBayesRunner.py b/backend/modules/performUQ/other/HeirBayesRunner.py new file mode 100644 index 00000000..c8c43ecb --- /dev/null +++ b/backend/modules/performUQ/other/HeirBayesRunner.py @@ -0,0 +1,234 @@ +# written: Aakash Bangalore Satish @ NHERI SimCenter, UC Berkeley + +import json +import os +import sys +import time + +from uqRunner import UqRunner + + +class HeirBayesRunner(UqRunner): + def __init__(self) -> None: + super().__init__() + self.n_samples = 0 + self.n_burn_in = 0 + self.tuning_interval = 0 + + def storeUQData(self, uqData): + for val in uqData["Parameters"]: + if val["name"] == "File To Run": + self.file_to_run = val["value"] + elif val["name"] == "# Samples": + self.n_samples = int(val["value"]) + elif val["name"] == "# Burn-in": + self.n_burn_in = int(val["value"]) + elif val["name"] == "Tuning Interval": + self.tuning_interval = int(val["value"]) + + def performHeirBayesSampling(self): + self.dir_name = os.path.dirname(self.file_to_run) + sys.path.append(self.dir_name) + from main_real_data_34 import HeirBayesSampler as heir_code + + self.heir_code = heir_code() + + self.trace, self.time_taken, self.inf_object = self.heir_code.perform_sampling( + n_samples=self.n_samples, + n_burn_in=self.n_burn_in, + tuning_interval=self.tuning_interval, + ) + + def saveResultsToPklFile(self): + self.saved_pickle_filename = self.heir_code.save_results( + self.trace, self.time_taken, self.inf_object, prefix="realdata_filtered" + ) + + def createHeadingStringsList(self): + self.params = ["fy", "E", "b", "cR1", "cR2", "a1", "a3"] + self.num_params = len(self.params) + self.num_coupons = 34 + + # self.heading_list = ["interface"] + self.heading_list = ["Sample#", "interface"] + for i in range(self.num_coupons): + for j in range(self.num_params): + self.heading_list.append( + "".join(["Coupon_", str(i + 1), "_", self.params[j]]) + ) + + for row in range(self.num_params): + for col in range(row + 1): + self.heading_list.append("".join(["Cov_", str(row + 1), str(col + 1)])) + + for par in self.params: + self.heading_list.append("".join(["Mean_", par])) + + for sig in range(self.num_coupons): + self.heading_list.append("".join(["ErrorVariance_", str(sig + 1)])) + + def makeHeadingRow(self, separator=", "): + self.headingRow = separator.join([item for item in self.heading_list]) + + def makeOneRowString(self, sample_num, sample, separator=", "): + initial_string = separator.join([str(sample_num), "1"]) + coupon_string = separator.join( + [ + str(sample[i][j]) + for i in range(self.num_coupons) + for j in range(self.num_params) + ] + ) + cov_string = separator.join( + [ + str(sample[self.num_coupons][row][col]) + for row in range(self.num_params) + for col in range(row + 1) + ] + ) + mean_string = separator.join( + [ + str(sample[self.num_coupons + 1][par_num]) + for par_num in range(self.num_params) + ] + ) + error_string = separator.join( + [str(sample[-1][coupon_num]) for coupon_num in range(self.num_coupons)] + ) + row_string = separator.join( + [initial_string, coupon_string, cov_string, mean_string, error_string] + ) + return row_string + + def makeTabularResultsFile(self, save_file_name="dakotaTab.out"): + self.createHeadingStringsList() + self.makeHeadingRow(separator=" ") + + cwd = os.getcwd() + save_file_dir = os.path.dirname(cwd) + save_file_full_path = os.path.join(save_file_dir, save_file_name) + with open(save_file_full_path, "w") as f: + f.write(self.headingRow) + f.write("\n") + for sample_num, sample in enumerate(self.trace): + row = self.makeOneRowString( + sample_num=sample_num, sample=sample, separator=" " + ) + f.write(row) + f.write("\n") + + def makePlots(self): + from temp_postprocess_real_data import make_plots + + make_plots(self.saved_pickle_filename) + + def startTimer(self): + self.startingTime = time.time() + + def computeTimeElapsed(self): + self.timeElapsed = time.time() - self.startingTime + + def printTimeElapsed(self): + self.computeTimeElapsed() + print("Time elapsed: {:0.2f} minutes".format(self.timeElapsed / 60)) + + def startSectionTimer(self): + self.sectionStartingTime = time.time() + + def resetSectionTimer(self): + self.startSectionTimer() + + def computeSectionTimeElapsed(self): + self.sectionTimeElapsed = time.time() - self.sectionStartingTime + + def printSectionTimeElapsed(self): + self.computeSectionTimeElapsed() + print("Time elapsed: {:0.2f} minutes".format(self.sectionTimeElapsed / 60)) + + @staticmethod + def printEndMessages(): + print("Heirarchical Bayesian estimation done!") + + def runUQ( + self, + uqData, + simulationData, + randomVarsData, + demandParams, + workingDir, + runType, + localAppDir, + remoteAppDir, + ): + """ + This function configures and runs hierarchical Bayesian estimation based on the + input UQ configuration, simulation configuration, random variables, + and requested demand parameters + + Input: + uqData: JsonObject that UQ options as input into the quoFEM GUI + simulationData: JsonObject that contains information on the analysis package to run and its + configuration as input in the quoFEM GUI + randomVarsData: JsonObject that specifies the input random variables, their distributions, + and associated parameters as input in the quoFEM GUI + demandParams: JsonObject that specifies the demand parameters as input in the quoFEM GUI + workingDir: Directory in which to run simulations and store temporary results + runType: Specifies whether computations are being run locally or on an HPC cluster + localAppDir: Directory containing apps for local run + remoteAppDir: Directory containing apps for remote run + """ + self.startTimer() + self.storeUQData(uqData=uqData) + os.chdir(workingDir) + self.performHeirBayesSampling() + self.saveResultsToPklFile() + self.makeTabularResultsFile() + self.makePlots() + self.printTimeElapsed() + self.printEndMessages() + + +class testRunUQ: + def __init__(self, json_file_path_string) -> None: + self.json_file_path_string = json_file_path_string + self.getUQData() + self.createRunner() + self.runTest() + + def getUQData(self): + with open(os.path.abspath(self.json_file_path_string), "r") as f: + input_data = json.load(f) + + self.uqData = input_data["UQ_Method"] + self.simulationData = input_data["fem"] + self.randomVarsData = input_data["randomVariables"] + self.demandParams = input_data["EDP"] + self.localAppDir = input_data["localAppDir"] + self.remoteAppDir = input_data["remoteAppDir"] + self.workingDir = input_data["workingDir"] + self.workingDir = os.path.join(self.workingDir, "tmp.SimCenter", "templateDir") + self.runType = "runningLocal" + + def createRunner(self): + self.runner = HeirBayesRunner() + + def runTest(self): + self.runner.runUQ( + uqData=self.uqData, + simulationData=self.simulationData, + randomVarsData=self.randomVarsData, + demandParams=self.demandParams, + workingDir=self.workingDir, + runType=self.runType, + localAppDir=self.localAppDir, + remoteAppDir=self.remoteAppDir, + ) + + +def main(): + filename = "/Users/aakash/Desktop/SimCenter/Joel/heirarchical-refactor/dakota.json" + testRunUQ(filename) + + +if __name__ == "__main__": + main() diff --git a/quoFEM.pro b/quoFEM.pro index 9d7ba4e2..ce9c010c 100644 --- a/quoFEM.pro +++ b/quoFEM.pro @@ -59,7 +59,8 @@ SOURCES += main.cpp\ FEM/FEAPpvParser.cpp \ FEM/OpenSeesPyParser.cpp \ FEM/surrogateGpFEM.cpp \ - FEM/surrogateGpParser.cpp + FEM/surrogateGpParser.cpp \ + FEM/CustomFEM.cpp \ HEADERS += WorkflowApp_quoFEM.h \ RunWidget.h \ @@ -73,6 +74,7 @@ HEADERS += WorkflowApp_quoFEM.h \ FEM/OpenSeesPyParser.h \ FEM/surrogateGpFEM.h \ FEM/surrogateGpParser.h \ + FEM/CustomFEM.h \ FORMS += mainwindow.ui