Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions mocos-gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
from model.ProjectHandler import ProjectHandler
from model.ProjectSettings import Cardinalities
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType
import PyQt5.QtCore
import logging
Expand All @@ -19,15 +19,15 @@ def shutdown():

qmlRegisterType(Cardinalities, "ProjectSettingTypes", 1, 0, "Cardinalities")

QGuiApplication.setAttribute(PyQt5.QtCore.Qt.AA_EnableHighDpiScaling, True)
QGuiApplication.setAttribute(PyQt5.QtCore.Qt.AA_UseHighDpiPixmaps, True)
QApplication.setAttribute(PyQt5.QtCore.Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(PyQt5.QtCore.Qt.AA_UseHighDpiPixmaps, True)

app = QGuiApplication([])
app = QApplication([])
engine = QQmlApplicationEngine()

app.aboutToQuit.connect(shutdown)
app.setApplicationName("MOCOS")
app.setOrganizationDomain("mocos.pl")
app.setApplicationName("MOCOS Simulator")
app.setOrganizationDomain("https://mocos.pl")

engine.rootContext().setContextProperty("projectHandler", projectHandler)
engine.rootContext().setContextProperty("initialConditions", projectHandler._settings.initialConditions)
Expand Down
18 changes: 10 additions & 8 deletions mocos-gui.pyproject
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@
"files": [
"mocos-gui.py",
"model/ApplicationSettings.py",
"model/charts.py",
"model/ConfigurationValidator.py",
"model/ProjectHandler.py",
"model/ProjectSettings.py",
"model/SimulationRunner.py",
"model/ConfigurationValidator.py",
"model/Utilities.py",
"views/ApplicationSettingsWindow.qml",
"views/MainWindow.qml",
"views/ModulationSettingsView.qml",
"views/PhoneTrackingSettingsView.qml",
"views/GeneralSettingsView.qml",
"views/InitialConditionsView.qml",
"views/TransmissionProbabilitiesView.qml",
"views/ContactTrackingSettingsView.qml",
"views/DailyInfectionsChartWindow.qml",
"views/DoubleNumField.qml",
"views/GeneralSettingsView.qml",
"views/InitialConditionsView.qml",
"views/IntNumField.qml",
"views/KernelEnablingButton.qml",
"views/LogWindow.qml",
"views/MainWindow.qml",
"views/ModulationSettingsView.qml",
"views/PhoneTrackingSettingsView.qml",
"views/SpreadingView.qml",
"views/TransmissionProbabilitiesView.qml",
"model/Configuration.schema",
"model/TanhParameters.schema"
]
]
}
41 changes: 27 additions & 14 deletions model/ApplicationSettings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from model.Utilities import formatPath, getOrEmptyStr, getOr
from model.Utilities import format_path, get_or_empty_str, get_or
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
import json
from enum import Enum
Expand Down Expand Up @@ -62,12 +62,12 @@ def __loadCliOptions(self):
appSettingsFileHandle.close()
if lines:
content = json.loads(lines)
self._juliaCommand = getOr(content, ApplicationSettings.PropertyNames.JULIA_COMMAND.value, "julia")
self._outputDaily = getOrEmptyStr(content, ApplicationSettings.PropertyNames.OUTPUT_DAILY.value)
self._outputSummary = getOrEmptyStr(content, ApplicationSettings.PropertyNames.OUTPUT_SUMMARY.value)
self._outputParamsDump = getOrEmptyStr(content, ApplicationSettings.PropertyNames.OUTPUT_PARAMS_DUMP.value)
self._outputRunDumpPrefix = getOrEmptyStr(content, ApplicationSettings.PropertyNames.OUTPUT_RUN_DUMP_PREFIX.value)
self._numOfThreads = getOr(content, ApplicationSettings.PropertyNames.NUM_OF_THREADS.value, 1)
self._juliaCommand = get_or(content, ApplicationSettings.PropertyNames.JULIA_COMMAND.value, "julia")
self._outputDaily = get_or_empty_str(content, ApplicationSettings.PropertyNames.OUTPUT_DAILY.value)
self._outputSummary = get_or_empty_str(content, ApplicationSettings.PropertyNames.OUTPUT_SUMMARY.value)
self._outputParamsDump = get_or_empty_str(content, ApplicationSettings.PropertyNames.OUTPUT_PARAMS_DUMP.value)
self._outputRunDumpPrefix = get_or_empty_str(content, ApplicationSettings.PropertyNames.OUTPUT_RUN_DUMP_PREFIX.value)
self._numOfThreads = get_or(content, ApplicationSettings.PropertyNames.NUM_OF_THREADS.value, 1)
if self._numOfThreads > self.getMaxNumOfThreads():
self._numOfThreads = self.getMaxNumOfThreads()
except OSError:
Expand Down Expand Up @@ -142,7 +142,7 @@ def outputRunDumpPrefix(self):
return self._outputRunDumpPrefix

def __isPathToOutputFileJld2Correct(self, relpath):
fullpath = formatPath(self._getworkdir() + "\\" + relpath)
fullpath = format_path(self._getworkdir() + "\\" + relpath)
return (os.path.dirname(fullpath) != fullpath
and relpath.endswith(".jld2")
and os.access(os.path.dirname(fullpath), os.W_OK))
Expand All @@ -169,7 +169,7 @@ def outputParamsDumpAcceptable(self):

@pyqtProperty(bool, notify=outputRunDumpPrefixAcceptabilityCheckReq)
def outputRunDumpPrefixAcceptable(self):
fullpath = formatPath(self._getworkdir() + "\\" + self._outputRunDumpPrefix)
fullpath = format_path(self._getworkdir() + "\\" + self._outputRunDumpPrefix)
return (self._outputRunDumpPrefix == ""
or (os.path.dirname(fullpath) != fullpath
and os.access(os.path.dirname(fullpath), os.W_OK)))
Expand All @@ -185,7 +185,7 @@ def recentFiles(self):
@juliaCommand.setter
def juliaCommand(self, cmd):
if cmd != "julia":
cmd = formatPath(cmd)
cmd = format_path(cmd)
if self._juliaCommand != cmd:
self._juliaCommand = cmd
self.__saveCliOptions()
Expand All @@ -194,7 +194,7 @@ def juliaCommand(self, cmd):

@outputDaily.setter
def outputDaily(self, path):
newpath = formatPath(path, makeRelativeTo=self._getworkdir())
newpath = format_path(path, makeRelativeTo=self._getworkdir())
if self._outputDaily != path:
self._outputDaily = newpath
self.__saveCliOptions()
Expand All @@ -203,7 +203,7 @@ def outputDaily(self, path):

@outputSummary.setter
def outputSummary(self, path):
newpath = formatPath(path, makeRelativeTo=self._getworkdir())
newpath = format_path(path, makeRelativeTo=self._getworkdir())
if self._outputSummary != path:
self._outputSummary = newpath
self.__saveCliOptions()
Expand All @@ -212,7 +212,7 @@ def outputSummary(self, path):

@outputParamsDump.setter
def outputParamsDump(self, path):
newpath = formatPath(path, makeRelativeTo=self._getworkdir())
newpath = format_path(path, makeRelativeTo=self._getworkdir())
if self._outputParamsDump != path:
self._outputParamsDump = newpath
self.__saveCliOptions()
Expand All @@ -221,7 +221,7 @@ def outputParamsDump(self, path):

@outputRunDumpPrefix.setter
def outputRunDumpPrefix(self, path):
newpath = formatPath(path, makeRelativeTo=self._getworkdir())
newpath = format_path(path, makeRelativeTo=self._getworkdir())
if self._outputRunDumpPrefix != path:
self._outputRunDumpPrefix = newpath
self.__saveCliOptions()
Expand All @@ -239,3 +239,16 @@ def numOfThreads(self, threadsNum):
@pyqtSlot(result=int)
def getMaxNumOfThreads(self):
return os.cpu_count()

def abs_path(self, propertyname):
if propertyname == ApplicationSettings.PropertyNames.NUM_OF_THREADS:
raise ValueError
if propertyname == ApplicationSettings.PropertyNames.OUTPUT_DAILY:
return "" if not self._outputDaily else format_path(self._getworkdir() + "//" + self._outputDaily)
if propertyname == ApplicationSettings.PropertyNames.OUTPUT_PARAMS_DUMP:
return "" if not self._outputParamsDump else format_path(self._getworkdir() + "//" + self._outputParamsDump)
if propertyname == ApplicationSettings.PropertyNames.OUTPUT_SUMMARY:
return "" if not self._outputSummary else format_path(self._getworkdir() + "//" + self._outputSummary)
if propertyname == ApplicationSettings.PropertyNames.OUTPUT_RUN_DUMP_PREFIX:
return "" if not self._outputRunDumpPrefix else format_path(self._getworkdir() + "//" + self._outputRunDumpPrefix)
raise NotImplementedError
76 changes: 60 additions & 16 deletions model/ProjectHandler.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# This Python file uses the following encoding: utf-8
from model.ProjectSettings import ModulationFunctions, ProjectSettings, EmptyModulationParams, ValueTypes
import json
from PyQt5.QtCore import QAbstractTableModel, pyqtSignal, pyqtSlot, Qt, QByteArray, QObject, QVariant, pyqtProperty
import os
from PyQt5.QtCore import QAbstractTableModel, pyqtSignal, pyqtSlot, Qt, QByteArray, QObject, QVariant, pyqtProperty
from model.ConfigurationValidator import ConfigurationValidator
from model.Utilities import formatPath
from model.Utilities import format_path
from model.ApplicationSettings import ApplicationSettings
from model.SimulationRunner import SimulationRunner
from jsonschema import ValidationError
import tempfile
import model.charts as charts


class FunctionParametersModel(QAbstractTableModel):
Expand Down Expand Up @@ -116,22 +117,34 @@ def notifyDataChanged(self, topLeft, bottomRight):


class ProjectHandler(QObject):
_settings = ProjectSettings()
_modulationModel = FunctionParametersModel()
_applicationSettings = None
_simulationRunner = None
_openedFilePath = None
_isOpenedConfModified = False
_isModifyingConfOngoing = True
_infectionTrajectories = None
showErrorMsg = pyqtSignal(str, arguments=['msg'])
modulationFunctionChanged = pyqtSignal()
openedNewConf = pyqtSignal()
openedConfModified = pyqtSignal()
infectionTrajectoriesChanged = pyqtSignal()
dailyInfectionsDataAvailableChanged = pyqtSignal()

updateDailyInfectionsChartBegin = pyqtSignal(str, arguments=['filename'])
addDailyInfectionSeries = pyqtSignal(str, list, arguments=['name', 'series'])
updateDailyInfectionsChartDone = pyqtSignal()
logDebug = pyqtSignal(str, arguments=['msg'])

def __init__(self):
super().__init__()
self._settings = ProjectSettings()
self._modulationModel = FunctionParametersModel()
self._applicationSettings = ApplicationSettings(lambda: self.workdir())
self._simulationRunner = SimulationRunner(lambda: self.workdir())
self._chart_preparer = charts.DailyInfectionsSeriesPreparer(
lambda: self._applicationSettings.abs_path(
ApplicationSettings.PropertyNames.OUTPUT_DAILY))
self.connect_signals()

def connect_signals(self):
def setModifiedToTrue(): return self.setOpenedConfModifiedIfModificationOngoing()
self._settings.generalSettings.numTrajectoriesChanged.connect(setModifiedToTrue)
self._settings.generalSettings.populationPathChanged.connect(setModifiedToTrue)
Expand All @@ -157,16 +170,29 @@ def setModifiedToTrue(): return self.setOpenedConfModifiedIfModificationOngoing(
self._settings.spreading.x0Changed.connect(setModifiedToTrue)
self._settings.spreading.truncationChanged.connect(setModifiedToTrue)
self._modulationModel.dataChanged.connect(lambda tr, bl, role: setModifiedToTrue())
self.openedNewConf.connect(self._applicationSettings.recheckPaths)
self.openedNewConf.connect(self._applicationSettings.recheckPaths, type=Qt.QueuedConnection)
self.openedNewConf.connect(self.dailyInfectionsDataAvailableChanged, type=Qt.QueuedConnection)
self._applicationSettings.outputDailyChanged.connect(
self.dailyInfectionsDataAvailableChanged, type=Qt.QueuedConnection)
self.dailyInfectionsDataAvailableChanged.connect(
self.prepareDailyInfectionsData, type=Qt.QueuedConnection)
self._simulationRunner.isRunningChanged.connect(
self.dailyInfectionsDataAvailableChanged, type=Qt.QueuedConnection)
self._chart_preparer.series_prepared.connect(self.addDailyInfectionSeries)
self._chart_preparer.preparing_begin.connect(
self.updateDailyInfectionsChartBegin, type=Qt.QueuedConnection)
self._chart_preparer.preparing_done.connect(
self.updateDailyInfectionsChartDone, type=Qt.QueuedConnection)
self._chart_preparer.log_debug.connect(self.logDebug)

def setOpenedConfModifiedIfModificationOngoing(self):
if self._isModifyingConfOngoing:
self.setOpenedConfModified(True)

def __saveConfToTempFile(self):
def _saveConfToTempFile(self):
fh = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False)
json.dump(self._settings.serialize(), fh, indent=4, ensure_ascii=False)
path = formatPath(fh.name)
path = format_path(fh.name)
fh.close()
return path

Expand All @@ -177,7 +203,7 @@ def workdir(self):

@pyqtSlot(str)
def saveAs(self, path):
path = formatPath(path)
path = format_path(path)
fh = open(path, "w", encoding='utf-8')
json.dump(self._settings.serialize(), fh, indent=4, ensure_ascii=False)
fh.close()
Expand All @@ -193,7 +219,8 @@ def quickSave(self):
@pyqtSlot(str)
def open(self, path):
try:
path = formatPath(path)
self._chart_preparer.stop()
path = format_path(path)
inputFileHandle = open(path, 'r', encoding='utf-8')
data = json.loads(inputFileHandle.read())
ConfigurationValidator.validateAgainstSchema(data)
Expand All @@ -210,6 +237,8 @@ def open(self, path):
self.showErrorMsg.emit("File: json schema is corrupted: {0}".format(str(error)))
except ValidationError as error:
self.showErrorMsg.emit("Unable to open file at: {0} due to wrong input data: {1}".format(path, str(error)))
finally:
inputFileHandle.close()

@pyqtSlot(result=str)
def getOpenedConfName(self):
Expand Down Expand Up @@ -254,10 +283,6 @@ def loadParamsForFunction(self, funcType, isModifyingConf):
else:
self._isModifyingConfOngoing = True

@pyqtSlot(str)
def setPopulationFilePath(self, path):
self._settings.generalSettings.populationPath = formatPath(path)

@pyqtSlot()
def runSimulation(self):
if not self._settings.generalSettings._populationPath:
Expand All @@ -273,9 +298,10 @@ def runSimulation(self):
or not self._applicationSettings.outputRunDumpPrefixAcceptable):
self.showErrorMsg.emit("Simulation can't be run: simulation settings are incorrect.")
return
self._chart_preparer.stop()
self._simulationRunner.openedFilePath = self._openedFilePath
if not self._simulationRunner.openedFilePath:
self._simulationRunner.openedFilePath = self.__saveConfToTempFile()
self._simulationRunner.openedFilePath = self._saveConfToTempFile()
self._simulationRunner.juliaCommand = self._applicationSettings.juliaCommand
self._simulationRunner.outputDaily = self._applicationSettings.outputDaily
self._simulationRunner.outputSummary = self._applicationSettings.outputSummary
Expand All @@ -287,3 +313,21 @@ def runSimulation(self):
@pyqtSlot()
def stopSimulation(self):
self._simulationRunner.stop()

@pyqtSlot()
def prepareDailyInfectionsData(self):
self._chart_preparer.start_preparing_data()

@pyqtProperty(bool, notify=dailyInfectionsDataAvailableChanged)
def isDailyInfectionsDataAvailable(self):
dailypath = self._applicationSettings.abs_path(ApplicationSettings.PropertyNames.OUTPUT_DAILY)
return charts.is_daily_infections_data_available(dailypath)

@pyqtSlot()
def addNextDailyInfectionSeries(self):
self._chart_preparer.on_series_added()

@pyqtSlot()
def beforeClosingMainWindow(self):
self.stopSimulation()
self._chart_preparer.stop()
4 changes: 2 additions & 2 deletions model/ProjectSettings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This Python file uses the following encoding: utf-8
from enum import Enum
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QVariant
from model.Utilities import formatPath
from model.Utilities import format_path


class GeneralSettings(QObject):
Expand Down Expand Up @@ -45,7 +45,7 @@ def numTrajectories(self, val):

@populationPath.setter
def populationPath(self, path):
path = formatPath(path)
path = format_path(path)
if self._populationPath != path:
self._populationPath = path
self.populationPathChanged.emit()
Expand Down
12 changes: 6 additions & 6 deletions model/SimulationRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import queue
import tempfile
from shutil import which
from model.Utilities import formatPath, ABS_PATH_TO_ADVANCED_CLI
from model.Utilities import format_path, ABS_PATH_TO_ADVANCED_CLI


def enqueueOutStream(output, q):
Expand Down Expand Up @@ -94,16 +94,16 @@ def _createCommand(self):
cmd.append(cliname)
if self.outputParamsDump:
cmd.append("--output-params-dump")
cmd.append(formatPath(self._getworkdir() + "\\" + self.outputParamsDump))
cmd.append(format_path(self._getworkdir() + "\\" + self.outputParamsDump))
if self.outputDaily:
cmd.append("--output-daily")
cmd.append(formatPath(self._getworkdir() + "\\" + self.outputDaily))
cmd.append(format_path(self._getworkdir() + "\\" + self.outputDaily))
if self.outputSummary:
cmd.append("--output-summary")
cmd.append(formatPath(self._getworkdir() + "\\" + self.outputSummary))
cmd.append(format_path(self._getworkdir() + "\\" + self.outputSummary))
if self.outputRunDumpPrefix:
cmd.append("--output-run-dump-prefix")
cmd.append(formatPath(self._getworkdir() + "\\" + self.outputRunDumpPrefix))
cmd.append(format_path(self._getworkdir() + "\\" + self.outputRunDumpPrefix))
cmd.append(self.openedFilePath)
return cmd

Expand Down Expand Up @@ -218,5 +218,5 @@ def stop(self):
self._process = None

def clean(self):
if self.openedFilePath.find(formatPath(tempfile.gettempdir())) != -1:
if self.openedFilePath.find(format_path(tempfile.gettempdir())) != -1:
os.remove(self.openedFilePath)
Loading