diff --git a/cxxfem/_src/femi.i b/cxxfem/_src/femi.i index 4cf565837dd454088d3fab725ba92256c5a5a558..1cfe5ad3620df2363001c48d6c4b1724e438a1e2 100644 --- a/cxxfem/_src/femi.i +++ b/cxxfem/_src/femi.i @@ -19,6 +19,7 @@ threads="1" #include "femExtractor.h" #include "wCppBuf2Py.h" +#include "OpenMP.h" #include <string> // #include <sstream> @@ -77,6 +78,7 @@ namespace std { %warnfilter(401); //Nothing known about base class 'std::basic_streambuf< char >' %include "wCppBuf2Py.h" %warnfilter(+401); +%include "OpenMP.h" // ----------- FEM CLASSES --------------- %include "fem.h" diff --git a/cxxfem/src/femSolver.cpp b/cxxfem/src/femSolver.cpp index 065bc22cc3a325ec45b349925c7cc3790c61e2fc..57f33c6e0363a5eb197dc1c84b2992aef9d0ec8d 100644 --- a/cxxfem/src/femSolver.cpp +++ b/cxxfem/src/femSolver.cpp @@ -33,7 +33,7 @@ Solver::Solver(Problem &_pbl) : pbl(_pbl), bcs.add("FREE"); bcs.add("PRESCRIBED"); - // std::cout << "#threads = " << OpenMP::get_max_threads() << '\n'; + std::cout << "#threads = " << OpenMP::get_max_threads() << '\n'; } Solver::~Solver() diff --git a/cxxfem/tests/beam3d.py b/cxxfem/tests/beam3d.py index d6de2a7baf4854357806dfd9ec1804d0e1d1e159..6e8f6f14997227909f09ebcb8634c85f218b6243 100644 --- a/cxxfem/tests/beam3d.py +++ b/cxxfem/tests/beam3d.py @@ -9,25 +9,25 @@ fem.debug_level(0) if __name__ == "__main__": # fine mesh - # pars = { - # "Lx": 5.0, # width - # "Ly": 1.0, # depth - # "Lz": 1.0, # height/thickness - # "nx": 60, - # "ny": 30, - # "nz": 30 - # } - - # same as python pars = { "Lx": 5.0, # width "Ly": 1.0, # depth "Lz": 1.0, # height/thickness - "nx": 15, - "ny": 2, - "nz": 10 + "nx": 60, + "ny": 30, + "nz": 30 } + # same as python + # pars = { + # "Lx": 5.0, # width + # "Ly": 1.0, # depth + # "Lz": 1.0, # height/thickness + # "nx": 15, + # "ny": 2, + # "nz": 10 + # } + # mono-element for debug # pars = { # "Lx": 5.0, # width diff --git a/cxxfem/utils.py b/cxxfem/utils.py index c3ab0dd7768f7880d48853c844994ba1cec0ae02..00a2ae1ed638491017bc3e81b41df61e9171ad11 100644 --- a/cxxfem/utils.py +++ b/cxxfem/utils.py @@ -37,13 +37,16 @@ def parseargs(): """ parses command line arguments """ + import os import argparse parser = argparse.ArgumentParser() - parser.add_argument( - "-v", "--verb", help="increase output verbosity", action="count", default=0) + # parser.add_argument( + # "-v", "--verb", help="increase output verbosity", action="count", default=0) parser.add_argument( "--nogui", help="disable any graphical output", action="store_true") - parser.add_argument("-k", help="nb of threads", type=int, default=1) - parser.add_argument('file', help='python files') + parser.add_argument( + "--post", help="display existing results", action="store_true") + parser.add_argument("-k", help="nb of threads", type=int, default=os.cpu_count()) + parser.add_argument('file', help='python file', type=str, nargs='?') args = parser.parse_args() return args diff --git a/fossil.png b/fossil.png new file mode 100644 index 0000000000000000000000000000000000000000..768b48306290e3d19f175a79776389b746d82c97 Binary files /dev/null and b/fossil.png differ diff --git a/fossils.py b/fossils.py index 3b0546f6dfa98a181a9a8b2e796f3787bc82b9e0..dda22a3bbb128fff87575e3882e40ebdf49e9df5 100644 --- a/fossils.py +++ b/fossils.py @@ -2,9 +2,8 @@ # -*- coding: utf-8 -*- # runs a test as if it was installed -import os -import platform - +# import os +# import platform # if 'Windows' in platform.uname(): # # remove tbb from the PATH # # otherwise: "Intel MKL FATAL ERROR: Cannot load mkl_intel_thread.dll" @@ -14,6 +13,56 @@ import platform # for p in corr_path: # print(p) +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from ui_fossils import Ui_Form + +class Window(QWidget, Ui_Form): + """Minimal GUI asking for a file and running it + """ + def __init__(self, parent=None): + super(Window, self).__init__(parent) + self.setupUi(self) + + # read Qt settings for the application + settings = QSettings() + # self.restoreGeometry(settings.value("Geometry", self.saveGeometry())) + self.inpFileLineEdit.setText(settings.value("inpFile", "")) + + self.action = 'cancelled' + + iconfile = os.path.join('fossil.png') + self.setWindowIcon(QIcon(iconfile)) + + def on_runPushButton_pressed(self): + # print("runPushButton...") + self.action = 'run' + self.close() + + def on_viewPushButton_pressed(self): + # print("viewPushButton...") + self.action = 'post' + self.close() + + def on_inpFilePushButton_pressed(self): + # print("inpFilePushButton...") + fname = QFileDialog.getOpenFileName( + None, 'Select input file', '', filter='Python File (*.py)') + # print(fname) + pyfile = fname[0] + if pyfile: + self.inpFileLineEdit.setText(QDir.toNativeSeparators(pyfile)) + + def closeEvent(self, event): + """save settings to registry and quit + """ + settings = QSettings() + # settings.setValue("Geometry", QVariant(self.saveGeometry())) + settings.setValue("inpfile", QVariant(self.inpFileLineEdit.text())) + event.accept() + + if __name__ == "__main__": import sys @@ -39,6 +88,18 @@ if __name__ == "__main__": args = cxxfem.parseargs() # set number of threads from the "-k" argument os.environ['OMP_NUM_THREADS'] = str(args.k) + cxxfem.set_num_threads(args.k) + + # try: + # ncpus1 = int(os.environ['NUMBER_OF_PROCESSORS']) + # print(f'{ncpus1} CPUs detected') + # import multiprocessing + # ncpus2 = multiprocessing.cpu_count() + # print(f'{ncpus2} CPUs detected') + # ncpus3 = os.cpu_count() + # print(f'{ncpus3} CPUs detected') + # except: + # pass # display env variables try: @@ -46,22 +107,41 @@ if __name__ == "__main__": except: print(f"OMP_NUM_THREADS=[not set]") - # run test file - testname = os.path.abspath(args.file) - testname = os.path.normcase(testname) # F:/ => f:/ on Windows - # create workspace - common = os.path.commonprefix((testname, thisdir + os.sep)) - resdir = testname[len(common):].replace(os.sep, "_") - resdir, ext = os.path.splitext(resdir) - wdir = os.path.join('workspace', resdir) - print('workspace=', wdir) - if not os.path.isdir(wdir): - os.makedirs(wdir) - os.chdir(wdir) - - tee = cxxfem.Tee('stdout.txt') # split streams - - if ext == '.py': - # run python script - __file__ = testname - exec(open(testname, encoding='utf-8').read()) + + # ask for a file if not given + if not args.file: + app = QApplication(sys.argv) + app.setOrganizationName("ULiege") + app.setApplicationName("fossils") + win = Window() + win.setWindowTitle("Fossils") + win.show() + app.lastWindowClosed.connect(app.quit) + app.exec_() + action = win.action + testname = win.inpFileLineEdit.text() + else: + action = 'run' + testname = os.path.abspath(args.file) + + print(f'action={action}') + if action=='run': + testname = os.path.normcase(testname) # F:/ => f:/ on Windows + print(f'testname = {testname}') + + # create workspace + common = os.path.commonprefix((testname, thisdir + os.sep)) + resdir = testname[len(common):].replace(os.sep, "_") + resdir, ext = os.path.splitext(resdir) + wdir = os.path.join('workspace', resdir) + print('workspace=', wdir) + if not os.path.isdir(wdir): + os.makedirs(wdir) + os.chdir(wdir) + + tee = cxxfem.Tee('stdout.txt') # split streams + + if ext == '.py': + # run python script + __file__ = testname + exec(open(testname, encoding='utf-8').read()) diff --git a/fossils.ui b/fossils.ui new file mode 100644 index 0000000000000000000000000000000000000000..6d8ab5a32d04d868f8165d5ced92cf30079ea717 --- /dev/null +++ b/fossils.ui @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>546</width> + <height>104</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="inpFilelabel"> + <property name="text"> + <string>input file</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="inpFileLineEdit"/> + </item> + <item> + <widget class="QPushButton" name="inpFilePushButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="runPushButton"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="viewPushButton"> + <property name="text"> + <string>View</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/gen_ui.py b/gen_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..4f71975f6e072ebd45e3ddfda4943ba96868f718 --- /dev/null +++ b/gen_ui.py @@ -0,0 +1,65 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Execute "pyuic". +# Ask for the location of it if not found. +# Store result in QSettings + +import sys +import subprocess +import os +import platform +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * + +def tryEXE(exe): + try: + print("dry run of '%s'..." % exe, end=' ') + with open(os.devnull, 'w') as FNULL: + subprocess.call([exe], stdout=FNULL, stderr=subprocess.STDOUT) + print("OK!") + return exe + except OSError: + print("failure!") + return "" + +def askUser(): + app = QApplication(sys.argv) + ext = '.*' if 'Windows' in platform.uname() else '' + fname = QFileDialog.getOpenFileName( + None, 'Select pyuic5', '', filter='Executable (pyuic5%s)' % ext) + exe = fname[0] + if not exe: + print('cancelled by user') + sys.exit(1) + print("user selected '%s'" % exe) + return exe + +if __name__ == "__main__": + + exe = 'pyuic5' + if tryEXE(exe): # try exe + pass + else: + # read settings + settings = QSettings("rboman", "progs") + #settings.remove("pyuic5") # clear stored value + exe = settings.value("pyuic5", "") + print("using value stored in settings: '%s'" % exe) + + if exe and tryEXE(exe): + pass + else: + exe = askUser() + if tryEXE(exe): + settings = QSettings("rboman", "progs") + settings.setValue("pyuic5", QVariant(exe)) + else: + raise Exception("'%s' does not work!" % exe) + + # run cmd + cmd = [exe, 'fossils.ui', '-o', 'ui_fossils.py'] + print("=> running", ' '.join(cmd)) + iop = subprocess.call(cmd) + print('iop =', iop) diff --git a/ui_fossils.py b/ui_fossils.py new file mode 100644 index 0000000000000000000000000000000000000000..8df8ef6f161ca5e693d5f3deb47829fc719f13d7 --- /dev/null +++ b/ui_fossils.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'fossils.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(546, 104) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.inpFilelabel = QtWidgets.QLabel(Form) + self.inpFilelabel.setObjectName("inpFilelabel") + self.horizontalLayout.addWidget(self.inpFilelabel) + self.inpFileLineEdit = QtWidgets.QLineEdit(Form) + self.inpFileLineEdit.setObjectName("inpFileLineEdit") + self.horizontalLayout.addWidget(self.inpFileLineEdit) + self.inpFilePushButton = QtWidgets.QPushButton(Form) + self.inpFilePushButton.setObjectName("inpFilePushButton") + self.horizontalLayout.addWidget(self.inpFilePushButton) + self.verticalLayout.addLayout(self.horizontalLayout) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.runPushButton = QtWidgets.QPushButton(Form) + self.runPushButton.setObjectName("runPushButton") + self.horizontalLayout_2.addWidget(self.runPushButton) + self.viewPushButton = QtWidgets.QPushButton(Form) + self.viewPushButton.setObjectName("viewPushButton") + self.horizontalLayout_2.addWidget(self.viewPushButton) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.inpFilelabel.setText(_translate("Form", "input file")) + self.inpFilePushButton.setText(_translate("Form", "...")) + self.runPushButton.setText(_translate("Form", "Run")) + self.viewPushButton.setText(_translate("Form", "View"))