From 9c447771b56200d064eaf7e6cd8f8715d70a1a05 Mon Sep 17 00:00:00 2001 From: Alex Hogg Date: Wed, 6 Nov 2019 10:32:11 +0000 Subject: [PATCH] Initial --- .gitattributes | 63 + .gitignore | 91 +- Directory.Build.props | 9 + RVis.Base/Check.cs | 597 ++++++++++ RVis.Base/Constant.cs | 30 + RVis.Base/Diagnostics/Logging.cs | 63 + RVis.Base/Enum.cs | 12 + RVis.Base/Extensions/ArrayExt.cs | 11 + RVis.Base/Extensions/BoolExt.cs | 8 + RVis.Base/Extensions/CollExt.cs | 96 ++ RVis.Base/Extensions/EnumExt.cs | 63 + RVis.Base/Extensions/FxExt.cs | 67 ++ RVis.Base/Extensions/LangExt.cs | 31 + RVis.Base/Extensions/NumExt.cs | 85 ++ RVis.Base/Extensions/ObjExt.cs | 17 + RVis.Base/Extensions/StrExt.cs | 135 +++ RVis.Base/Extensions/TupleExt.cs | 36 + RVis.Base/IO/DirectoryOps.cs | 76 ++ RVis.Base/Interface.cs | 12 + RVis.Base/Meta.cs | 53 + RVis.Base/Properties/Resources.Designer.cs | 180 +++ RVis.Base/Properties/Resources.resx | 172 +++ RVis.Base/RVis.Base.csproj | 66 ++ RVis.Base/UniStd/CliOpt.cs | 191 +++ RVis.Data/Extensions/TableExt.cs | 48 + RVis.Data/Fx/FxData.cs | 68 ++ RVis.Data/RVis.Data.csproj | 54 + RVis.Data/Table/DataColumn.cs | 26 + RVis.Data/Table/DataColumnBase.cs | 25 + RVis.Data/Table/DataTable.cs | 28 + RVis.Data/Table/DataTableBase.cs | 34 + RVis.Data/Table/Interface.cs | 32 + RVis.Data/Table/NumDataColumn.cs | 35 + RVis.Data/Table/NumDataTable.cs | 92 ++ RVis.Model/AssemblyInfo.cs | 3 + RVis.Model/Code/ElementCandidate.cs | 66 ++ RVis.Model/Code/Enum.cs | 12 + RVis.Model/Code/Interface.cs | 20 + RVis.Model/Code/ManagedImport.cs | 284 +++++ RVis.Model/Code/ParameterCandidate.cs | 76 ++ RVis.Model/Code/SymbolInfo.cs | 47 + RVis.Model/Code/ValueCandidate.cs | 66 ++ RVis.Model/Distribution/BetaDistribution.cs | 246 ++++ .../Distribution/BetaScaledDistribution.cs | 269 +++++ RVis.Model/Distribution/Distribution.cs | 125 ++ RVis.Model/Distribution/Enum.cs | 49 + RVis.Model/Distribution/GammaDistribution.cs | 245 ++++ RVis.Model/Distribution/Interface.cs | 31 + .../Distribution/InvariantDistribution.cs | 133 +++ .../Distribution/InverseGammaDistribution.cs | 155 +++ .../Distribution/LogNormalDistribution.cs | 246 ++++ .../Distribution/LogUniformDistribution.cs | 196 ++++ RVis.Model/Distribution/NormalDistribution.cs | 255 ++++ .../Distribution/RandomNumberGenerator.cs | 14 + .../Distribution/StudentTDistribution.cs | 261 +++++ .../Distribution/UniformDistribution.cs | 202 ++++ RVis.Model/Extensions/DistributionExt.cs | 31 + RVis.Model/Extensions/SimDataExt.cs | 31 + RVis.Model/Extensions/SimDataLogExt.cs | 10 + RVis.Model/Extensions/SimEvidenceExt.cs | 118 ++ RVis.Model/Extensions/SimExt.Data.cs | 85 ++ RVis.Model/Extensions/SimExt.Element.cs | 31 + RVis.Model/Extensions/SimExt.IO.cs | 50 + RVis.Model/Extensions/SimExt.Parameter.cs | 74 ++ RVis.Model/Extensions/SimExt.cs | 55 + RVis.Model/Logger.cs | 10 + RVis.Model/R/Interface.cs | 150 +++ RVis.Model/R/RVisServerPool.cs | 167 +++ RVis.Model/R/ServerLicense.cs | 37 + RVis.Model/R/ServerSlot.cs | 9 + RVis.Model/R/SvcRes.cs | 178 +++ RVis.Model/RVis.Model.csproj | 59 + RVis.Model/Sim/Constant.cs | 13 + RVis.Model/Sim/EventArgs.cs | 12 + RVis.Model/Sim/Sim.cs | 113 ++ RVis.Model/Sim/SimCode.cs | 56 + RVis.Model/Sim/SimConfig.cs | 95 ++ RVis.Model/Sim/SimElement.cs | 67 ++ RVis.Model/Sim/SimInput.cs | 94 ++ RVis.Model/Sim/SimLibrary.cs | 134 +++ RVis.Model/Sim/SimOutput.cs | 83 ++ RVis.Model/Sim/SimParameter.cs | 103 ++ RVis.Model/Sim/SimValue.cs | 70 ++ RVis.Model/Sim/Simulation.cs | 139 +++ RVis.Model/Sim/TOML/TSimCode.cs | 11 + RVis.Model/Sim/TOML/TSimConfig.cs | 19 + RVis.Model/Sim/TOML/TSimElement.cs | 13 + RVis.Model/Sim/TOML/TSimInput.cs | 8 + RVis.Model/Sim/TOML/TSimOutput.cs | 7 + RVis.Model/Sim/TOML/TSimParameter.cs | 13 + RVis.Model/Sim/TOML/TSimValue.cs | 9 + RVis.Model/SimData/Enum.cs | 14 + RVis.Model/SimData/Impl/SimData.Dispose.cs | 30 + RVis.Model/SimData/Impl/SimData.ExecInt.cs | 112 ++ .../SimData/Impl/SimData.OutputRequest.cs | 221 ++++ .../SimData/Impl/SimData.ServiceLoop.cs | 168 +++ RVis.Model/SimData/Impl/SimData.SimOutput.cs | 34 + RVis.Model/SimData/Interface.cs | 20 + RVis.Model/SimData/OutputRequest.cs | 31 + RVis.Model/SimData/SimData.cs | 171 +++ RVis.Model/SimData/SimDataItem.cs | 60 + RVis.Model/SimDataLog/SimDataLogArchive.cs | 190 +++ RVis.Model/SimDataLog/SimDataLogEntry.cs | 49 + RVis.Model/SimDataLog/SimDataSessionLog.cs | 230 ++++ .../SimEvidence/DTO/EvidenceSourceDTO.cs | 17 + .../SimEvidence/DTO/EvidenceSourcesDTO.cs | 43 + .../SimEvidence/Impl/SimObservationsSet.cs | 187 +++ RVis.Model/SimEvidence/Interface.cs | 33 + RVis.Model/SimEvidence/SimEvidence.cs | 321 +++++ RVis.Model/SimEvidence/SimEvidenceSource.cs | 61 + RVis.Model/SimEvidence/SimObservations.cs | 63 + RVis.Model/SimEvidence/SimObservationsSet.cs | 36 + RVis.sln | 351 ++++++ SetAppVerVariables.ps1 | 7 + .../ErrorModel/Impl/ErrorModelImpl.cs | 19 + .../ErrorModel/Impl/UnitTestTruncNorm.cs | 40 + .../UnitTestHeteroscedasticExpErrorModel.cs | 40 + .../UnitTestHeteroscedasticPowerErrorModel.cs | 43 + .../ErrorModel/UnitTestLogNormalErrorModel.cs | 36 + .../ErrorModel/UnitTestNormalErrorModel.cs | 36 + Test/Estimation.Test/Estimation.Test.csproj | 92 ++ .../Model/Extensions/UnitTestCollExt.cs | 57 + .../Properties/AssemblyInfo.cs | 20 + .../Extensions/UnitTestArrayExt.cs | 39 + .../Extensions/UnitTestBoolExt.cs | 28 + .../Extensions/UnitTestCollExt.cs | 193 +++ .../Extensions/UnitTestEnumExt.cs | 113 ++ .../Extensions/UnitTestFxExt.cs | 129 ++ .../Extensions/UnitTestLangExt.cs | 52 + .../Extensions/UnitTestNumExt.cs | 99 ++ .../Extensions/UnitTestObjExt.cs | 17 + .../Extensions/UnitTestStrExt.cs | 182 +++ .../Extensions/UnitTestTupleExt.cs | 17 + .../RVis.Base.Test/IO/UnitTestDirectoryOps.cs | 20 + Test/RVis.Base.Test/RVis.Base.Test.csproj | 23 + Test/RVis.Base.Test/UniStd/UnitTestCliOpt.cs | 60 + Test/RVis.Base.Test/UnitTestCheck.cs | 21 + Test/RVis.Base.Test/UnitTestMeta.cs | 18 + .../Properties/AssemblyInfo.cs | 20 + Test/RVis.Client.Test/RVis.Client.Test.csproj | 94 ++ Test/RVis.Client.Test/UnitTestSource.cs | 47 + Test/RVis.Data.Test/RVis.Data.Test.csproj | 21 + Test/RVis.Data.Test/UnitTestTable.cs | 33 + .../Code/UnitTestElementCandidate.cs | 20 + .../Code/UnitTestManagedImport.cs | 94 ++ .../Code/UnitTestParameterCandidate.cs | 26 + .../Code/UnitTestValueCandidate.cs | 26 + .../Distribution/DistributionImpl.cs | 19 + .../Distribution/UnitTestBetaDistribution.cs | 92 ++ .../UnitTestBetaScaledDistribution.cs | 93 ++ .../Distribution/UnitTestDistribution.cs | 85 ++ .../Distribution/UnitTestGammaDistribution.cs | 92 ++ .../UnitTestLogNormalDistribution.cs | 93 ++ .../UnitTestLogUniformDistribution.cs | 94 ++ .../UnitTestNormalDistribution.cs | 92 ++ .../UnitTestStudentTDistribution.cs | 92 ++ .../UnitTestUniformDistribution.cs | 92 ++ .../Extensions/UnitTestDistributionExt.cs | 38 + .../Extensions/UnitTestSimEvidenceExt.cs | 89 ++ .../Properties/AssemblyInfo.cs | 20 + Test/RVis.Model.Test/R/UnitTestR.cs | 66 ++ Test/RVis.Model.Test/RVis.Model.Test.csproj | 122 ++ Test/RVis.Model.Test/Sim/UnitTestSimConfig.cs | 34 + Test/RVis.Model.Test/Sim/UnitTestSimInput.cs | 30 + .../SimEvidence/UnitTestSimObservationsSet.cs | 41 + .../RVis.ROps.Test/Properties/AssemblyInfo.cs | 20 + Test/RVis.ROps.Test/RVis.ROps.Test.csproj | 95 ++ Test/RVis.ROps.Test/UnitTestCode.cs | 167 +++ Test/RVis.ROps.Test/UnitTestPersistence.cs | 56 + .../Module/UnitTestModuleInfo.cs | 43 + .../Properties/AssemblyInfo.cs | 20 + .../RVisUI.Model.Test.csproj | 66 ++ Test/SimLibrary/CubicExec/.rvis/config.toml | 41 + Test/SimLibrary/CubicExec/cubic.R | 24 + Test/SimLibrary/InspectExec/Inspect.R | 41 + Test/SimLibrary/InspectTmpl/Inspect.R | 44 + Test/Test.Shared/Test.Shared.projitems | 14 + Test/Test.Shared/Test.Shared.shproj | 13 + Test/Test.Shared/TestData.cs | 24 + UI/Module/Estimation/AssemblyInfo.cs | 3 + .../Dialogs/IterationOptionsDialog.xaml | 132 +++ .../Dialogs/IterationOptionsDialog.xaml.cs | 28 + .../Controls/Views/DesignDigestsView.xaml | 101 ++ .../Controls/Views/DesignDigestsView.xaml.cs | 28 + .../Estimation/Controls/Views/DesignView.xaml | 204 ++++ .../Controls/Views/DesignView.xaml.cs | 15 + .../HeteroscedasticExpErrorModelView.xaml | 131 +++ .../HeteroscedasticExpErrorModelView.xaml.cs | 28 + .../HeteroscedasticPowerErrorModelView.xaml | 155 +++ ...HeteroscedasticPowerErrorModelView.xaml.cs | 28 + .../ErrorModels/LogNormalErrorModelView.xaml | 95 ++ .../LogNormalErrorModelView.xaml.cs | 28 + .../ErrorModels/NormalErrorModelView.xaml | 98 ++ .../ErrorModels/NormalErrorModelView.xaml.cs | 28 + .../ErrorModels/OutputErrorModelView.xaml | 85 ++ .../ErrorModels/OutputErrorModelView.xaml.cs | 28 + .../Estimation/Controls/Views/FitView.xaml | 75 ++ .../Estimation/Controls/Views/FitView.xaml.cs | 28 + .../Controls/Views/LikelihoodView.xaml | 236 ++++ .../Controls/Views/LikelihoodView.xaml.cs | 15 + .../Controls/Views/PosteriorView.xaml | 79 ++ .../Controls/Views/PosteriorView.xaml.cs | 28 + .../Estimation/Controls/Views/PriorsView.xaml | 167 +++ .../Controls/Views/PriorsView.xaml.cs | 15 + .../Controls/Views/SimulationView.xaml | 142 +++ .../Controls/Views/SimulationView.xaml.cs | 28 + UI/Module/Estimation/Design/ViewModel.cs | 19 + .../ViewModels/DesignDigestsViewModel.cs | 26 + .../Design/ViewModels/DesignViewModel.cs | 47 + .../Dialog/IterationOptionsViewModel.cs | 20 + .../ErrorModel/OutputErrorViewModel.cs | 23 + .../Design/ViewModels/FitViewModel.cs | 67 ++ .../Design/ViewModels/LikelihoodViewModel.cs | 109 ++ .../Design/ViewModels/PosteriorViewModel.cs | 80 ++ .../Design/ViewModels/PriorsViewModel.cs | 30 + .../Design/ViewModels/SimulationViewModel.cs | 95 ++ UI/Module/Estimation/ErrorModel/Enum.cs | 20 + UI/Module/Estimation/ErrorModel/ErrorModel.cs | 128 ++ .../HeteroscedasticExpErrorModel.cs | 230 ++++ .../HeteroscedasticPowerErrorModel.cs | 277 +++++ .../Estimation/ErrorModel/Impl/TruncNorm.cs | 36 + UI/Module/Estimation/ErrorModel/Interface.cs | 16 + .../ErrorModel/LogNormalErrorModel.cs | 119 ++ .../Estimation/ErrorModel/NormalErrorModel.cs | 117 ++ UI/Module/Estimation/Estimation.csproj | 359 ++++++ UI/Module/Estimation/Logger.cs | 10 + UI/Module/Estimation/Model/DesignDigest.cs | 16 + .../Estimation/Model/EstimationDesign.cs | 48 + .../Estimation/Model/EstimationDesigns.cs | 141 +++ .../Estimation/Model/Extensions/CollExt.cs | 48 + .../Model/Impl/EstimationDesign.Create.cs | 52 + .../Model/Impl/EstimationDesign.Persist.cs | 216 ++++ .../Estimation/Model/MCMC/Impl/McmcChain.cs | 63 + UI/Module/Estimation/Model/MCMC/McmcChain.cs | 733 ++++++++++++ UI/Module/Estimation/Model/MCMC/McmcSim.cs | 394 +++++++ UI/Module/Estimation/Model/ModelExt.cs | 55 + UI/Module/Estimation/Model/ModelOutput.cs | 20 + UI/Module/Estimation/Model/ModelParameter.cs | 45 + .../Estimation/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 62 + .../Estimation/Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 30 + .../Estimation/Properties/Settings.settings | 7 + UI/Module/Estimation/Service.cs | 29 + UI/Module/Estimation/State/ChainState.cs | 33 + UI/Module/Estimation/State/DesignState.cs | 31 + UI/Module/Estimation/State/Impl/ChainState.cs | 211 ++++ .../Estimation/State/Impl/ModuleState.cs | 250 ++++ .../Estimation/State/Impl/PosteriorState.cs | 54 + UI/Module/Estimation/State/LikelihoodState.cs | 17 + UI/Module/Estimation/State/ModuleState.cs | 201 ++++ UI/Module/Estimation/State/OutputState.cs | 77 ++ UI/Module/Estimation/State/PosteriorState.cs | 14 + UI/Module/Estimation/State/PriorsState.cs | 17 + UI/Module/Estimation/State/SimulationState.cs | 17 + UI/Module/Estimation/State/StateExt.cs | 39 + UI/Module/Estimation/View.xaml | 83 ++ UI/Module/Estimation/View.xaml.cs | 28 + UI/Module/Estimation/ViewModel.cs | 710 +++++++++++ .../ViewModels/DesignDigestsViewModel.cs | 184 +++ .../Estimation/ViewModels/DesignViewModel.cs | 465 ++++++++ .../Dialog/IterationOptionsViewModel.cs | 72 ++ .../HeteroscedasticExpErrorViewModel.cs | 197 ++++ .../HeteroscedasticPowerErrorViewModel.cs | 221 ++++ .../ErrorModel/LogNormalErrorViewModel.cs | 147 +++ .../ErrorModel/NormalErrorViewModel.cs | 147 +++ .../ErrorModel/OutputErrorViewModel.cs | 207 ++++ .../Estimation/ViewModels/FitViewModel.cs | 342 ++++++ .../ViewModels/Impl/ChainViewModel.cs | 24 + .../ViewModels/Impl/DesignDigestViewModel.cs | 17 + .../ViewModels/Impl/ObservationsViewModel.cs | 50 + .../ViewModels/Impl/OutputViewModel.cs | 38 + .../ViewModels/Impl/PriorViewModel.cs | 38 + UI/Module/Estimation/ViewModels/Interface.cs | 225 ++++ .../ViewModels/LikelihoodViewModel.cs | 489 ++++++++ .../ViewModels/PosteriorViewModel.cs | 379 ++++++ .../Estimation/ViewModels/PriorsViewModel.cs | 288 +++++ .../ViewModels/SimulationViewModel.cs | 856 ++++++++++++++ UI/Module/Estimation/app.config | 11 + .../Dialogs/ImportObservationsDialog.xaml | 269 +++++ .../Dialogs/ImportObservationsDialog.xaml.cs | 28 + .../Evidence/Controls/Views/BrowseView.xaml | 138 +++ .../Controls/Views/BrowseView.xaml.cs | 28 + .../Evidence/Controls/Views/ManageView.xaml | 112 ++ .../Controls/Views/ManageView.xaml.cs | 28 + UI/Module/Evidence/Design/ViewModel.cs | 11 + .../Design/ViewModels/BrowseViewModel.cs | 106 ++ .../Dialog/ImportObservationsViewModel.cs | 36 + .../Design/ViewModels/ManageViewModel.cs | 41 + UI/Module/Evidence/Evidence.csproj | 229 ++++ UI/Module/Evidence/Interface.cs | 83 ++ UI/Module/Evidence/Properties/AssemblyInfo.cs | 36 + .../Evidence/Properties/Resources.Designer.cs | 62 + UI/Module/Evidence/Properties/Resources.resx | 117 ++ .../Evidence/Properties/Settings.Designer.cs | 30 + .../Evidence/Properties/Settings.settings | 7 + UI/Module/Evidence/Service.cs | 29 + UI/Module/Evidence/State/ModuleState.cs | 266 +++++ UI/Module/Evidence/View.xaml | 69 ++ UI/Module/Evidence/View.xaml.cs | 28 + UI/Module/Evidence/ViewModel.cs | 230 ++++ .../Evidence/ViewModels/BrowseViewModel.cs | 388 ++++++ .../Impl/ObservationsColumnViewModel.cs | 60 + .../Dialog/ImportObservationsViewModel.cs | 314 +++++ .../Impl/EvidenceSourceViewModel.cs | 18 + .../ViewModels/Impl/ObservationsViewModel.cs | 44 + .../ViewModels/Impl/SubjectViewModel.cs | 31 + .../Evidence/ViewModels/ManageViewModel.cs | 227 ++++ UI/Module/Plot/Controls/ChartGrid.cs | 162 +++ .../Controls/Views/Impl/DepVarConfigView.xaml | 273 +++++ .../Views/Impl/DepVarConfigView.xaml.cs | 28 + .../Views/Impl/ParameterBrowseView.xaml | 97 ++ .../Views/Impl/ParameterBrowseView.xaml.cs | 28 + .../Views/Impl/ParameterEditView.xaml | 200 ++++ .../Views/Impl/ParameterEditView.xaml.cs | 28 + .../Views/Impl/TraceDataPlotView.xaml | 161 +++ .../Views/Impl/TraceDataPlotView.xaml.cs | 33 + .../Plot/Controls/Views/OutputsView.xaml | 139 +++ .../Plot/Controls/Views/OutputsView.xaml.cs | 28 + .../Plot/Controls/Views/ParametersView.xaml | 172 +++ .../Controls/Views/ParametersView.xaml.cs | 61 + UI/Module/Plot/Controls/Views/TraceView.xaml | 194 +++ .../Plot/Controls/Views/TraceView.xaml.cs | 28 + UI/Module/Plot/Design/Data.cs | 21 + UI/Module/Plot/Design/ViewModel.cs | 11 + .../ViewModels/Impl/DepVarConfigViewModel.cs | 47 + .../ViewModels/Impl/LogEntryViewModel.cs | 15 + .../ViewModels/Impl/OutputGroupViewModel.cs | 13 + .../ViewModels/Impl/TraceDataPlotViewModel.cs | 115 ++ .../Design/ViewModels/OutputsViewModel.cs | 45 + .../Design/ViewModels/ParameterViewModel.cs | 72 ++ .../Design/ViewModels/ParametersViewModel.cs | 41 + .../Plot/Design/ViewModels/TraceViewModel.cs | 58 + UI/Module/Plot/Enum.cs | 10 + UI/Module/Plot/Group/OutputGroup.cs | 202 ++++ UI/Module/Plot/Group/OutputGroupStore.cs | 128 ++ UI/Module/Plot/Interface.cs | 132 +++ UI/Module/Plot/Logger.cs | 10 + UI/Module/Plot/Plot.csproj | 278 +++++ UI/Module/Plot/Properties/AssemblyInfo.cs | 36 + .../Plot/Properties/Resources.Designer.cs | 62 + UI/Module/Plot/Properties/Resources.resx | 117 ++ .../Plot/Properties/Settings.Designer.cs | 30 + UI/Module/Plot/Properties/Settings.settings | 7 + UI/Module/Plot/Service.cs | 29 + UI/Module/Plot/State/DepVarConfigState.cs | 75 ++ UI/Module/Plot/State/Impl/ModuleState.cs | 151 +++ UI/Module/Plot/State/Impl/ModuleStateDTO.cs | 40 + UI/Module/Plot/State/ModuleState.cs | 67 ++ UI/Module/Plot/State/ParameterEditState.cs | 71 ++ UI/Module/Plot/State/TraceDataPlotState.cs | 73 ++ UI/Module/Plot/State/TraceState.cs | 21 + UI/Module/Plot/View.xaml | 54 + UI/Module/Plot/View.xaml.cs | 28 + UI/Module/Plot/ViewModel.cs | 505 ++++++++ .../ViewModels/Impl/DepVarConfigViewModel.cs | 358 ++++++ .../Plot/ViewModels/Impl/LogEntryViewModel.cs | 28 + .../ViewModels/Impl/OutputGroupViewModel.cs | 23 + .../ViewModels/Impl/ParameterViewModel.cs | 273 +++++ UI/Module/Plot/ViewModels/Impl/Parameters.cs | 268 +++++ .../Impl/SelectableItemViewModel.cs | 41 + .../ViewModels/Impl/TraceDataPlotViewModel.cs | 629 ++++++++++ UI/Module/Plot/ViewModels/OutputsViewModel.cs | 196 ++++ .../Plot/ViewModels/ParametersViewModel.cs | 64 + UI/Module/Plot/ViewModels/TraceViewModel.cs | 500 ++++++++ .../Controls/Views/DesignDigestsView.xaml | 101 ++ .../Controls/Views/DesignDigestsView.xaml.cs | 28 + .../Sampling/Controls/Views/DesignView.xaml | 184 +++ .../Controls/Views/DesignView.xaml.cs | 28 + .../Views/Impl/NoDesignActivityView.xaml | 20 + .../Views/Impl/NoDesignActivityView.xaml.cs | 28 + .../Views/Impl/OutputsDesignActivityView.xaml | 82 ++ .../Impl/OutputsDesignActivityView.xaml.cs | 28 + .../Views/Impl/ParameterSamplingView.xaml | 22 + .../Views/Impl/ParameterSamplingView.xaml.cs | 28 + .../Views/Impl/SamplesDesignActivityView.xaml | 55 + .../Impl/SamplesDesignActivityView.xaml.cs | 28 + .../Controls/Views/ParametersView.xaml | 159 +++ .../Controls/Views/ParametersView.xaml.cs | 28 + UI/Module/Sampling/Design/Model/Data.cs | 230 ++++ UI/Module/Sampling/Design/ViewModel.cs | 11 + .../ViewModels/DesignDigestsViewModel.cs | 25 + .../Design/ViewModels/DesignViewModel.cs | 51 + .../Design/ViewModels/ParametersViewModel.cs | 28 + UI/Module/Sampling/Interface.cs | 88 ++ UI/Module/Sampling/Logger.cs | 10 + UI/Module/Sampling/Model/DesignDigest.cs | 16 + UI/Module/Sampling/Model/DesignParameter.cs | 16 + .../Sampling/Model/Impl/SamplingDesign.cs | 243 ++++ UI/Module/Sampling/Model/ModelExt.cs | 53 + UI/Module/Sampling/Model/SamplingDesign.cs | 26 + UI/Module/Sampling/Model/SamplingDesigns.cs | 133 +++ UI/Module/Sampling/Properties/AssemblyInfo.cs | 36 + .../Sampling/Properties/Resources.Designer.cs | 63 + UI/Module/Sampling/Properties/Resources.resx | 120 ++ .../Sampling/Properties/Settings.Designer.cs | 30 + .../Sampling/Properties/Settings.settings | 7 + UI/Module/Sampling/Sampling.csproj | 276 +++++ UI/Module/Sampling/Service.cs | 29 + UI/Module/Sampling/State/DesignState.cs | 31 + UI/Module/Sampling/State/Impl/ModuleState.cs | 148 +++ UI/Module/Sampling/State/ModuleState.cs | 103 ++ UI/Module/Sampling/State/ParametersState.cs | 17 + UI/Module/Sampling/View.xaml | 54 + UI/Module/Sampling/View.xaml.cs | 28 + UI/Module/Sampling/ViewModel.cs | 450 +++++++ .../ViewModels/DesignDigestsViewModel.cs | 172 +++ .../Sampling/ViewModels/DesignViewModel.cs | 812 +++++++++++++ .../Design/DesignActivityViewModelBase.cs | 65 + .../ViewModels/Impl/Design/Interface.cs | 33 + .../Impl/Design/NoDesignActivityViewModel.cs | 7 + .../Design/OutputsDesignActivityViewModel.cs | 205 ++++ .../Design/SamplesDesignActivityViewModel.cs | 20 + .../ViewModels/Impl/DesignDigestViewModel.cs | 17 + .../Impl/ParameterSamplingViewModel.cs | 118 ++ .../ViewModels/Impl/ParameterViewModel.cs | 38 + .../ViewModels/ParametersViewModel.cs | 271 +++++ .../Controls/Dialogs/ChartOptionsDialog.xaml | 369 ++++++ .../Dialogs/ChartOptionsDialog.xaml.cs | 28 + .../Controls/Views/DesignDigestsView.xaml | 101 ++ .../Controls/Views/DesignDigestsView.xaml.cs | 28 + .../Controls/Views/DesignView.xaml | 223 ++++ .../Controls/Views/DesignView.xaml.cs | 15 + .../Controls/Views/EffectsView.xaml | 205 ++++ .../Controls/Views/EffectsView.xaml.cs | 28 + .../Controls/Views/ParametersView.xaml | 159 +++ .../Controls/Views/ParametersView.xaml.cs | 15 + .../Controls/Views/VarianceView.xaml | 77 ++ .../Controls/Views/VarianceView.xaml.cs | 28 + UI/Module/Sensitivity/Design/Model/Data.cs | 90 ++ UI/Module/Sensitivity/Design/ViewModel.cs | 15 + .../ViewModels/DesignDigestsViewModel.cs | 26 + .../Design/ViewModels/DesignViewModel.cs | 60 + .../Design/ViewModels/EffectsViewModel.cs | 59 + .../Design/ViewModels/ParametersViewModel.cs | 28 + .../Design/ViewModels/VarianceViewModel.cs | 76 ++ UI/Module/Sensitivity/Logger.cs | 10 + UI/Module/Sensitivity/Model/DesignDigest.cs | 16 + .../Sensitivity/Model/DesignParameter.cs | 27 + .../Model/Impl/SensitivityDesign.Create.cs | 33 + .../Model/Impl/SensitivityDesign.Measure.cs | 158 +++ .../Model/Impl/SensitivityDesign.persist.cs | 262 +++++ UI/Module/Sensitivity/Model/ModelExt.cs | 37 + .../Sensitivity/Model/SensitivityDesign.cs | 26 + .../Sensitivity/Model/SensitivityDesigns.cs | 174 +++ .../Sensitivity/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 97 ++ .../Sensitivity/Properties/Resources.resx | 153 +++ .../Properties/Settings.Designer.cs | 30 + .../Sensitivity/Properties/Settings.settings | 7 + UI/Module/Sensitivity/Sensitivity.csproj | 278 +++++ UI/Module/Sensitivity/Service.cs | 30 + UI/Module/Sensitivity/State/DesignState.cs | 24 + .../Sensitivity/State/Impl/ModuleState.cs | 274 +++++ UI/Module/Sensitivity/State/LowryState.cs | 53 + UI/Module/Sensitivity/State/MeasuresState.cs | 26 + UI/Module/Sensitivity/State/ModuleState.cs | 125 ++ .../Sensitivity/State/ParametersState.cs | 17 + UI/Module/Sensitivity/State/TraceState.cs | 110 ++ UI/Module/Sensitivity/View.xaml | 67 ++ UI/Module/Sensitivity/View.xaml.cs | 28 + UI/Module/Sensitivity/ViewModel.cs | 472 ++++++++ .../ViewModels/DesignDigestsViewModel.cs | 184 +++ .../Sensitivity/ViewModels/DesignViewModel.cs | 1041 +++++++++++++++++ .../Dialog/ChartOptionsViewModel.cs | 329 ++++++ .../ViewModels/EffectsViewModel.cs | 568 +++++++++ UI/Module/Sensitivity/ViewModels/Enum.cs | 10 + .../ViewModels/Impl/DesignDigestViewModel.cs | 17 + .../ViewModels/Impl/DesignViewModel.static.cs | 267 +++++ .../ViewModels/Impl/LowryPlotData.cs | 184 +++ .../ViewModels/Impl/LowryViewModel.cs | 411 +++++++ .../ViewModels/Impl/MeasuresOps.cs | 118 ++ .../ViewModels/Impl/OxyColorData.cs | 31 + .../ViewModels/Impl/ParameterViewModel.cs | 38 + .../ViewModels/Impl/TraceViewModel.cs | 505 ++++++++ UI/Module/Sensitivity/ViewModels/Interface.cs | 142 +++ .../ViewModels/ParametersViewModel.cs | 278 +++++ .../ViewModels/VarianceViewModel.cs | 496 ++++++++ UI/Module/deps/3rd/OxyPlot.Wpf.dll | Bin 0 -> 161280 bytes UI/Module/deps/3rd/OxyPlot.Wpf.pdb | Bin 0 -> 450048 bytes UI/Module/deps/3rd/OxyPlot.dll | Bin 0 -> 546304 bytes UI/Module/deps/3rd/OxyPlot.pdb | Bin 0 -> 221664 bytes .../Controls/Media/TiffExporter.cs | 152 +++ .../Distributions/BetaDistributionView.xaml | 142 +++ .../BetaDistributionView.xaml.cs | 28 + .../BetaScaledDistributionView.xaml | 176 +++ .../BetaScaledDistributionView.xaml.cs | 28 + .../Distributions/GammaDistributionView.xaml | 142 +++ .../GammaDistributionView.xaml.cs | 28 + .../InvariantDistributionView.xaml | 85 ++ .../InvariantDistributionView.xaml.cs | 28 + .../InverseGammaDistributionView.xaml | 100 ++ .../InverseGammaDistributionView.xaml.cs | 28 + .../LogNormalDistributionView.xaml | 142 +++ .../LogNormalDistributionView.xaml.cs | 28 + .../LogUniformDistributionView.xaml | 100 ++ .../LogUniformDistributionView.xaml.cs | 28 + .../Distributions/NormalDistributionView.xaml | 142 +++ .../NormalDistributionView.xaml.cs | 28 + .../StudentTDistributionView.xaml | 160 +++ .../StudentTDistributionView.xaml.cs | 28 + .../UniformDistributionView.xaml | 100 ++ .../UniformDistributionView.xaml.cs | 28 + .../Views/ParameterDistributionView.xaml | 97 ++ .../Views/ParameterDistributionView.xaml.cs | 28 + UI/RVisUI.AppInf/Design/Model/AppService.cs | 89 ++ UI/RVisUI.AppInf/Design/Model/AppSettings.cs | 37 + .../ParameterDistributionViewModel.cs | 24 + .../Extensions/ParameterStateExt.cs | 21 + UI/RVisUI.AppInf/Extensions/PlotExt.cs | 199 ++++ .../Model/State/ParameterState.cs | 109 ++ UI/RVisUI.AppInf/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 63 + UI/RVisUI.AppInf/Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 26 + UI/RVisUI.AppInf/Properties/Settings.settings | 7 + UI/RVisUI.AppInf/RVisUI.AppInf.csproj | 284 +++++ UI/RVisUI.AppInf/Resources/HelpButton.xaml | 20 + .../BetaDistributionViewModel.cs | 251 ++++ .../BetaScaledDistributionViewModel.cs | 276 +++++ .../GammaDistributionViewModel.cs | 238 ++++ .../Impl/DistributionLineSeries.cs | 109 ++ .../Impl/DistributionViewModelBase.cs | 81 ++ .../Distributions/Impl/Interface.cs | 97 ++ .../InvariantDistributionViewModel.cs | 111 ++ .../InverseGammaDistributionViewModel.cs | 223 ++++ .../LogNormalDistributionViewModel.cs | 279 +++++ .../LogUniformDistributionViewModel.cs | 212 ++++ .../NormalDistributionViewModel.cs | 268 +++++ .../StudentTDistributionViewModel.cs | 274 +++++ .../UniformDistributionViewModel.cs | 204 ++++ UI/RVisUI.AppInf/ViewModels/Interface.cs | 12 + .../ParameterDistributionViewModel.cs | 215 ++++ UI/RVisUI.AppInf/app.config | 11 + UI/RVisUI.Ioc/Design/AppModule.cs | 11 + .../Infrastructure/NinjectBootstrapper.cs | 24 + UI/RVisUI.Ioc/Design/ViewModelModule.cs | 14 + .../Infrastructure/NinjectBootstrapper.cs | 25 + .../Infrastructure/NinjectServiceLocator.cs | 36 + UI/RVisUI.Ioc/Interface.cs | 10 + UI/RVisUI.Ioc/RVisUI.Ioc.csproj | 53 + UI/RVisUI.Ioc/ServiceModule.cs | 11 + UI/RVisUI.Ioc/ViewModelLocator.cs | 66 ++ UI/RVisUI.Ioc/ViewModelModule.cs | 14 + UI/RVisUI.Model/AssemblyInfo.cs | 3 + UI/RVisUI.Model/Attribute.cs | 38 + UI/RVisUI.Model/Enum.cs | 65 + UI/RVisUI.Model/EventArgs.cs | 12 + UI/RVisUI.Model/Extensions/FxExt.cs | 96 ++ UI/RVisUI.Model/Extensions/ModelExt.cs | 27 + UI/RVisUI.Model/Extensions/ModuleExt.cs | 33 + UI/RVisUI.Model/Extensions/SharedStateExt.cs | 100 ++ UI/RVisUI.Model/Interface.cs | 147 +++ UI/RVisUI.Model/Logger.cs | 10 + UI/RVisUI.Model/Module/ModuleInfo.cs | 150 +++ UI/RVisUI.Model/Mvvm/ViewModelBase.cs | 56 + UI/RVisUI.Model/RVisUI.Model.csproj | 55 + UI/RVisUI.Model/ReactiveSafeInvoke.cs | 44 + .../SharedState/SimElementSharedState.cs | 28 + .../SharedState/SimObservationsSharedState.cs | 28 + .../SharedState/SimParameterSharedState.cs | 52 + UI/RVisUI.Model/SharedState/SimSharedState.cs | 530 +++++++++ .../SharedState/SimSharedStateBuilder.cs | 136 +++ .../Dialog/ChangeDescriptionUnitViewModel.cs | 21 + .../Dialog/CommonConfigurationViewModel.cs | 50 + .../Design/Dialog/ImportExecViewModel.cs | 47 + .../Design/Dialog/ImportTmplViewModel.cs | 61 + UI/RVisUI.Mvvm/Design/HomeViewModel.cs | 13 + .../Design/Impl/SharedStateViewModel.cs | 46 + .../Design/Impl/SimulationViewModel.cs | 15 + .../Design/ImportSimulationViewModel.cs | 55 + .../Design/ModuleNotSupportedViewModel.cs | 12 + .../Design/SelectSimulationViewModel.cs | 31 + .../Design/SimulationHomeViewModel.cs | 56 + .../Dialog/ChangeDescriptionUnitViewModel.cs | 72 ++ .../Dialog/CommonConfigurationViewModel.cs | 54 + UI/RVisUI.Mvvm/Dialog/Impl/ModuleViewModel.cs | 48 + UI/RVisUI.Mvvm/Dialog/ImportExecViewModel.cs | 250 ++++ UI/RVisUI.Mvvm/Dialog/ImportTmplViewModel.cs | 265 +++++ UI/RVisUI.Mvvm/Dialog/SelectExecViewModel.cs | 62 + UI/RVisUI.Mvvm/FailedStartUpViewModel.cs | 8 + UI/RVisUI.Mvvm/HomeViewModel.cs | 34 + .../Impl/ElementCandidateViewModel.cs | 88 ++ .../Impl/ParameterCandidateViewModel.cs | 67 ++ UI/RVisUI.Mvvm/Impl/SharedStateViewModel.cs | 362 ++++++ UI/RVisUI.Mvvm/Impl/SimulationViewModel.cs | 22 + UI/RVisUI.Mvvm/ImportSimulationViewModel.cs | 480 ++++++++ UI/RVisUI.Mvvm/Infrastructure/Interface.cs | 201 ++++ UI/RVisUI.Mvvm/Infrastructure/Logger.cs | 10 + UI/RVisUI.Mvvm/LibraryViewModel.cs | 53 + UI/RVisUI.Mvvm/ModuleNotSupportedViewModel.cs | 18 + UI/RVisUI.Mvvm/RVisUI.Mvvm.csproj | 57 + UI/RVisUI.Mvvm/SelectSimulationViewModel.cs | 116 ++ UI/RVisUI.Mvvm/SimulationHomeViewModel.cs | 265 +++++ UI/RVisUI.WPF/Adorners/AdornedControl.cs | 385 ++++++ UI/RVisUI.WPF/Adorners/Enum.cs | 12 + .../Adorners/FrameworkElementAdorner.cs | 327 ++++++ .../Behavior/ListBoxSelectionBehavior.cs | 199 ++++ .../Converter/BrushToHexConverter.cs | 30 + .../Converter/ColorToBrushConverter.cs | 22 + .../Converter/ElementTypeConverter.cs | 37 + .../Converter/EnumMatchToBooleanConverter.cs | 34 + .../Converter/GridLengthConverter.cs | 26 + .../Converter/InverseBooleanConverter.cs | 21 + .../Converter/KeyEventArgsConverter.cs | 28 + UI/RVisUI.WPF/Converter/SigFigsConverter.cs | 26 + UI/RVisUI.WPF/Converter/UpperCaseConverter.cs | 22 + UI/RVisUI.WPF/DepProps/Behaviour.cs | 240 ++++ UI/RVisUI.WPF/DepProps/Content.cs | 43 + UI/RVisUI.WPF/DepProps/Impl/DataPipe.cs | 48 + .../DepProps/Impl/DataPipeCollection.cs | 12 + UI/RVisUI.WPF/DepProps/Layout.cs | 80 ++ .../Interactivity/EventArgsCommandAction.cs | 96 ++ UI/RVisUI.WPF/Properties/AssemblyInfo.cs | 38 + .../Properties/Resources.Designer.cs | 62 + UI/RVisUI.WPF/Properties/Resources.resx | 117 ++ UI/RVisUI.WPF/Properties/Settings.Designer.cs | 30 + UI/RVisUI.WPF/Properties/Settings.settings | 7 + UI/RVisUI.WPF/RVisUI.Wpf.csproj | 140 +++ .../Resources/DesignModeResourceDictionary.cs | 23 + UI/RVisUI/App.config | 92 ++ UI/RVisUI/App.xaml | 42 + UI/RVisUI/App.xaml.cs | 35 + UI/RVisUI/AppImpl/App.Diagnostics.cs | 62 + UI/RVisUI/AppImpl/App.Startup.cs | 66 ++ UI/RVisUI/AppImpl/App.Window.cs | 30 + .../Controls/ActiveViewTemplateSelector.cs | 44 + UI/RVisUI/Controls/AppSettingsFlyout.xaml | 68 ++ UI/RVisUI/Controls/AppSettingsFlyout.xaml.cs | 29 + UI/RVisUI/Controls/BusyOverlay.xaml | 111 ++ UI/RVisUI/Controls/BusyOverlay.xaml.cs | 109 ++ .../Controls/Dialogs/AskYesNoDialog.xaml | 71 ++ .../Controls/Dialogs/AskYesNoDialog.xaml.cs | 49 + .../Dialogs/ChangeDescriptionUnitDialog.xaml | 173 +++ .../ChangeDescriptionUnitDialog.xaml.cs | 27 + .../Dialogs/CommonConfigurationDialog.xaml | 212 ++++ .../Dialogs/CommonConfigurationDialog.xaml.cs | 28 + .../Dialogs/ConfigureModulesDialog.xaml | 163 +++ .../Dialogs/ConfigureModulesDialog.xaml.cs | 85 ++ .../Controls/Dialogs/ImportExecDialog.xaml | 414 +++++++ .../Controls/Dialogs/ImportExecDialog.xaml.cs | 35 + .../Controls/Dialogs/ImportTmplDialog.xaml | 440 +++++++ .../Controls/Dialogs/ImportTmplDialog.xaml.cs | 35 + UI/RVisUI/Controls/Dialogs/NotifyDialog.xaml | 95 ++ .../Controls/Dialogs/NotifyDialog.xaml.cs | 36 + .../Controls/Dialogs/SelectExecDialog.xaml | 101 ++ .../Controls/Dialogs/SelectExecDialog.xaml.cs | 28 + .../Controls/Views/FailedStartUpView.xaml | 42 + .../Controls/Views/FailedStartUpView.xaml.cs | 28 + UI/RVisUI/Controls/Views/HomeView.xaml | 35 + UI/RVisUI/Controls/Views/HomeView.xaml.cs | 28 + .../Controls/Views/Impl/PaletteSelector.xaml | 475 ++++++++ .../Views/Impl/PaletteSelector.xaml.cs | 28 + .../Controls/Views/Impl/SharedStateView.xaml | 377 ++++++ .../Views/Impl/SharedStateView.xaml.cs | 28 + .../Controls/Views/ImportSimulationView.xaml | 308 +++++ .../Views/ImportSimulationView.xaml.cs | 28 + UI/RVisUI/Controls/Views/LibraryView.xaml | 54 + UI/RVisUI/Controls/Views/LibraryView.xaml.cs | 28 + .../Views/ModuleNotSupportedView.xaml | 58 + .../Views/ModuleNotSupportedView.xaml.cs | 28 + .../Controls/Views/SelectSimulationView.xaml | 226 ++++ .../Views/SelectSimulationView.xaml.cs | 36 + .../Controls/Views/SimulationHomeView.xaml | 202 ++++ .../Controls/Views/SimulationHomeView.xaml.cs | 33 + UI/RVisUI/DepProps/Content/Content.cs | 79 ++ UI/RVisUI/DepProps/Layout/Layout.cs | 25 + UI/RVisUI/Extensions/SettingsExt.cs | 122 ++ UI/RVisUI/Interop/SafeNativeMethods.cs | 64 + UI/RVisUI/Ioc/AppModule.cs | 17 + UI/RVisUI/Ioc/AppService.cs | 328 ++++++ UI/RVisUI/Ioc/AppSettings.cs | 236 ++++ UI/RVisUI/Ioc/AppState.cs | 73 ++ UI/RVisUI/Ioc/Impl/AppState.Lifecycle.cs | 196 ++++ UI/RVisUI/Ioc/Impl/AppState.Module.cs | 108 ++ UI/RVisUI/Ioc/Impl/AppState.Sim.cs | 85 ++ UI/RVisUI/Ioc/Impl/AppState.UI.cs | 128 ++ UI/RVisUI/Ioc/Mvvm/AppSettingsViewModel.cs | 302 +++++ UI/RVisUI/Ioc/Mvvm/Enum.cs | 10 + UI/RVisUI/Ioc/Mvvm/Impl/HueViewModel.cs | 36 + UI/RVisUI/Ioc/Mvvm/Impl/SwatchViewModel.cs | 22 + UI/RVisUI/Ioc/Mvvm/Interface.cs | 49 + UI/RVisUI/Ioc/Mvvm/ModuleConfigViewModel.cs | 50 + UI/RVisUI/MainWindow.xaml | 188 +++ UI/RVisUI/MainWindow.xaml.cs | 217 ++++ UI/RVisUI/Properties/AssemblyInfo.cs | 37 + UI/RVisUI/Properties/Resources.Designer.cs | 71 ++ UI/RVisUI/Properties/Resources.resx | 117 ++ UI/RVisUI/Properties/Settings.Designer.cs | 206 ++++ UI/RVisUI/Properties/Settings.settings | 51 + UI/RVisUI/RVisUI.csproj | 380 ++++++ UI/RVisUI/Resources/app.ico | Bin 0 -> 29119 bytes Version/VersionInfo.cs | 11 + WinR/RVis.Client/NativeMethods.cs | 128 ++ WinR/RVis.Client/Properties/AssemblyInfo.cs | 19 + WinR/RVis.Client/RVis.Client.csproj | 99 ++ WinR/RVis.Client/RVisClientProxy.Callback.cs | 22 + WinR/RVis.Client/RVisClientProxy.Source.cs | 171 +++ .../RVis.Client/RVisServer.RVisClientProxy.cs | 97 ++ WinR/RVis.Client/RVisServer.cs | 289 +++++ WinR/RVis.ROps/Impl/ROpsApi.Evaluate.cs | 177 +++ WinR/RVis.ROps/Impl/ROpsApi.Input.cs | 24 + WinR/RVis.ROps/Impl/ROpsApi.Inspect.cs | 357 ++++++ WinR/RVis.ROps/Impl/ROpsApi.Retrieve.cs | 127 ++ WinR/RVis.ROps/Properties/AssemblyInfo.cs | 19 + .../Properties/Resources.Designer.cs | 218 ++++ WinR/RVis.ROps/Properties/Resources.resx | 228 ++++ WinR/RVis.ROps/ROpsApi.Code.cs | 107 ++ WinR/RVis.ROps/ROpsApi.Persistence.cs | 46 + WinR/RVis.ROps/ROpsApi.Sim.cs | 188 +++ WinR/RVis.ROps/ROpsApi.cs | 72 ++ WinR/RVis.ROps/RVis.ROps.csproj | 110 ++ WinR/RVis.Server/App.config | 31 + WinR/RVis.Server/Program.cs | 118 ++ WinR/RVis.Server/Properties/AssemblyInfo.cs | 20 + WinR/RVis.Server/RVis.Server.csproj | 117 ++ WinR/RVis.Server/RVisService.Duplex.cs | 18 + WinR/RVis.Server/RVisService.Source.cs | 253 ++++ WinR/RVis.Server/RVisService.cs | 106 ++ WinR/WinR.Shared/ServiceHelper.cs | 20 + WinR/WinR.Shared/WinR.Shared.projitems | 14 + WinR/WinR.Shared/WinR.Shared.shproj | 13 + azure-pipelines.yml | 81 ++ clean.bat | 32 + clean_x64.bat | 32 + prepare_archive.bat | 75 ++ prepare_archive_x64.bat | 75 ++ prepare_release.bat | 104 ++ release_notes.md | 3 + 729 files changed, 73563 insertions(+), 77 deletions(-) create mode 100644 .gitattributes create mode 100644 Directory.Build.props create mode 100644 RVis.Base/Check.cs create mode 100644 RVis.Base/Constant.cs create mode 100644 RVis.Base/Diagnostics/Logging.cs create mode 100644 RVis.Base/Enum.cs create mode 100644 RVis.Base/Extensions/ArrayExt.cs create mode 100644 RVis.Base/Extensions/BoolExt.cs create mode 100644 RVis.Base/Extensions/CollExt.cs create mode 100644 RVis.Base/Extensions/EnumExt.cs create mode 100644 RVis.Base/Extensions/FxExt.cs create mode 100644 RVis.Base/Extensions/LangExt.cs create mode 100644 RVis.Base/Extensions/NumExt.cs create mode 100644 RVis.Base/Extensions/ObjExt.cs create mode 100644 RVis.Base/Extensions/StrExt.cs create mode 100644 RVis.Base/Extensions/TupleExt.cs create mode 100644 RVis.Base/IO/DirectoryOps.cs create mode 100644 RVis.Base/Interface.cs create mode 100644 RVis.Base/Meta.cs create mode 100644 RVis.Base/Properties/Resources.Designer.cs create mode 100644 RVis.Base/Properties/Resources.resx create mode 100644 RVis.Base/RVis.Base.csproj create mode 100644 RVis.Base/UniStd/CliOpt.cs create mode 100644 RVis.Data/Extensions/TableExt.cs create mode 100644 RVis.Data/Fx/FxData.cs create mode 100644 RVis.Data/RVis.Data.csproj create mode 100644 RVis.Data/Table/DataColumn.cs create mode 100644 RVis.Data/Table/DataColumnBase.cs create mode 100644 RVis.Data/Table/DataTable.cs create mode 100644 RVis.Data/Table/DataTableBase.cs create mode 100644 RVis.Data/Table/Interface.cs create mode 100644 RVis.Data/Table/NumDataColumn.cs create mode 100644 RVis.Data/Table/NumDataTable.cs create mode 100644 RVis.Model/AssemblyInfo.cs create mode 100644 RVis.Model/Code/ElementCandidate.cs create mode 100644 RVis.Model/Code/Enum.cs create mode 100644 RVis.Model/Code/Interface.cs create mode 100644 RVis.Model/Code/ManagedImport.cs create mode 100644 RVis.Model/Code/ParameterCandidate.cs create mode 100644 RVis.Model/Code/SymbolInfo.cs create mode 100644 RVis.Model/Code/ValueCandidate.cs create mode 100644 RVis.Model/Distribution/BetaDistribution.cs create mode 100644 RVis.Model/Distribution/BetaScaledDistribution.cs create mode 100644 RVis.Model/Distribution/Distribution.cs create mode 100644 RVis.Model/Distribution/Enum.cs create mode 100644 RVis.Model/Distribution/GammaDistribution.cs create mode 100644 RVis.Model/Distribution/Interface.cs create mode 100644 RVis.Model/Distribution/InvariantDistribution.cs create mode 100644 RVis.Model/Distribution/InverseGammaDistribution.cs create mode 100644 RVis.Model/Distribution/LogNormalDistribution.cs create mode 100644 RVis.Model/Distribution/LogUniformDistribution.cs create mode 100644 RVis.Model/Distribution/NormalDistribution.cs create mode 100644 RVis.Model/Distribution/RandomNumberGenerator.cs create mode 100644 RVis.Model/Distribution/StudentTDistribution.cs create mode 100644 RVis.Model/Distribution/UniformDistribution.cs create mode 100644 RVis.Model/Extensions/DistributionExt.cs create mode 100644 RVis.Model/Extensions/SimDataExt.cs create mode 100644 RVis.Model/Extensions/SimDataLogExt.cs create mode 100644 RVis.Model/Extensions/SimEvidenceExt.cs create mode 100644 RVis.Model/Extensions/SimExt.Data.cs create mode 100644 RVis.Model/Extensions/SimExt.Element.cs create mode 100644 RVis.Model/Extensions/SimExt.IO.cs create mode 100644 RVis.Model/Extensions/SimExt.Parameter.cs create mode 100644 RVis.Model/Extensions/SimExt.cs create mode 100644 RVis.Model/Logger.cs create mode 100644 RVis.Model/R/Interface.cs create mode 100644 RVis.Model/R/RVisServerPool.cs create mode 100644 RVis.Model/R/ServerLicense.cs create mode 100644 RVis.Model/R/ServerSlot.cs create mode 100644 RVis.Model/R/SvcRes.cs create mode 100644 RVis.Model/RVis.Model.csproj create mode 100644 RVis.Model/Sim/Constant.cs create mode 100644 RVis.Model/Sim/EventArgs.cs create mode 100644 RVis.Model/Sim/Sim.cs create mode 100644 RVis.Model/Sim/SimCode.cs create mode 100644 RVis.Model/Sim/SimConfig.cs create mode 100644 RVis.Model/Sim/SimElement.cs create mode 100644 RVis.Model/Sim/SimInput.cs create mode 100644 RVis.Model/Sim/SimLibrary.cs create mode 100644 RVis.Model/Sim/SimOutput.cs create mode 100644 RVis.Model/Sim/SimParameter.cs create mode 100644 RVis.Model/Sim/SimValue.cs create mode 100644 RVis.Model/Sim/Simulation.cs create mode 100644 RVis.Model/Sim/TOML/TSimCode.cs create mode 100644 RVis.Model/Sim/TOML/TSimConfig.cs create mode 100644 RVis.Model/Sim/TOML/TSimElement.cs create mode 100644 RVis.Model/Sim/TOML/TSimInput.cs create mode 100644 RVis.Model/Sim/TOML/TSimOutput.cs create mode 100644 RVis.Model/Sim/TOML/TSimParameter.cs create mode 100644 RVis.Model/Sim/TOML/TSimValue.cs create mode 100644 RVis.Model/SimData/Enum.cs create mode 100644 RVis.Model/SimData/Impl/SimData.Dispose.cs create mode 100644 RVis.Model/SimData/Impl/SimData.ExecInt.cs create mode 100644 RVis.Model/SimData/Impl/SimData.OutputRequest.cs create mode 100644 RVis.Model/SimData/Impl/SimData.ServiceLoop.cs create mode 100644 RVis.Model/SimData/Impl/SimData.SimOutput.cs create mode 100644 RVis.Model/SimData/Interface.cs create mode 100644 RVis.Model/SimData/OutputRequest.cs create mode 100644 RVis.Model/SimData/SimData.cs create mode 100644 RVis.Model/SimData/SimDataItem.cs create mode 100644 RVis.Model/SimDataLog/SimDataLogArchive.cs create mode 100644 RVis.Model/SimDataLog/SimDataLogEntry.cs create mode 100644 RVis.Model/SimDataLog/SimDataSessionLog.cs create mode 100644 RVis.Model/SimEvidence/DTO/EvidenceSourceDTO.cs create mode 100644 RVis.Model/SimEvidence/DTO/EvidenceSourcesDTO.cs create mode 100644 RVis.Model/SimEvidence/Impl/SimObservationsSet.cs create mode 100644 RVis.Model/SimEvidence/Interface.cs create mode 100644 RVis.Model/SimEvidence/SimEvidence.cs create mode 100644 RVis.Model/SimEvidence/SimEvidenceSource.cs create mode 100644 RVis.Model/SimEvidence/SimObservations.cs create mode 100644 RVis.Model/SimEvidence/SimObservationsSet.cs create mode 100644 RVis.sln create mode 100644 SetAppVerVariables.ps1 create mode 100644 Test/Estimation.Test/ErrorModel/Impl/ErrorModelImpl.cs create mode 100644 Test/Estimation.Test/ErrorModel/Impl/UnitTestTruncNorm.cs create mode 100644 Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticExpErrorModel.cs create mode 100644 Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticPowerErrorModel.cs create mode 100644 Test/Estimation.Test/ErrorModel/UnitTestLogNormalErrorModel.cs create mode 100644 Test/Estimation.Test/ErrorModel/UnitTestNormalErrorModel.cs create mode 100644 Test/Estimation.Test/Estimation.Test.csproj create mode 100644 Test/Estimation.Test/Model/Extensions/UnitTestCollExt.cs create mode 100644 Test/Estimation.Test/Properties/AssemblyInfo.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestArrayExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestBoolExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestCollExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestEnumExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestFxExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestLangExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestNumExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestObjExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestStrExt.cs create mode 100644 Test/RVis.Base.Test/Extensions/UnitTestTupleExt.cs create mode 100644 Test/RVis.Base.Test/IO/UnitTestDirectoryOps.cs create mode 100644 Test/RVis.Base.Test/RVis.Base.Test.csproj create mode 100644 Test/RVis.Base.Test/UniStd/UnitTestCliOpt.cs create mode 100644 Test/RVis.Base.Test/UnitTestCheck.cs create mode 100644 Test/RVis.Base.Test/UnitTestMeta.cs create mode 100644 Test/RVis.Client.Test/Properties/AssemblyInfo.cs create mode 100644 Test/RVis.Client.Test/RVis.Client.Test.csproj create mode 100644 Test/RVis.Client.Test/UnitTestSource.cs create mode 100644 Test/RVis.Data.Test/RVis.Data.Test.csproj create mode 100644 Test/RVis.Data.Test/UnitTestTable.cs create mode 100644 Test/RVis.Model.Test/Code/UnitTestElementCandidate.cs create mode 100644 Test/RVis.Model.Test/Code/UnitTestManagedImport.cs create mode 100644 Test/RVis.Model.Test/Code/UnitTestParameterCandidate.cs create mode 100644 Test/RVis.Model.Test/Code/UnitTestValueCandidate.cs create mode 100644 Test/RVis.Model.Test/Distribution/DistributionImpl.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestBetaDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestBetaScaledDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestGammaDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestLogNormalDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestLogUniformDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestNormalDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestStudentTDistribution.cs create mode 100644 Test/RVis.Model.Test/Distribution/UnitTestUniformDistribution.cs create mode 100644 Test/RVis.Model.Test/Extensions/UnitTestDistributionExt.cs create mode 100644 Test/RVis.Model.Test/Extensions/UnitTestSimEvidenceExt.cs create mode 100644 Test/RVis.Model.Test/Properties/AssemblyInfo.cs create mode 100644 Test/RVis.Model.Test/R/UnitTestR.cs create mode 100644 Test/RVis.Model.Test/RVis.Model.Test.csproj create mode 100644 Test/RVis.Model.Test/Sim/UnitTestSimConfig.cs create mode 100644 Test/RVis.Model.Test/Sim/UnitTestSimInput.cs create mode 100644 Test/RVis.Model.Test/SimEvidence/UnitTestSimObservationsSet.cs create mode 100644 Test/RVis.ROps.Test/Properties/AssemblyInfo.cs create mode 100644 Test/RVis.ROps.Test/RVis.ROps.Test.csproj create mode 100644 Test/RVis.ROps.Test/UnitTestCode.cs create mode 100644 Test/RVis.ROps.Test/UnitTestPersistence.cs create mode 100644 Test/RVisUI.Model.Test/Module/UnitTestModuleInfo.cs create mode 100644 Test/RVisUI.Model.Test/Properties/AssemblyInfo.cs create mode 100644 Test/RVisUI.Model.Test/RVisUI.Model.Test.csproj create mode 100644 Test/SimLibrary/CubicExec/.rvis/config.toml create mode 100644 Test/SimLibrary/CubicExec/cubic.R create mode 100644 Test/SimLibrary/InspectExec/Inspect.R create mode 100644 Test/SimLibrary/InspectTmpl/Inspect.R create mode 100644 Test/Test.Shared/Test.Shared.projitems create mode 100644 Test/Test.Shared/Test.Shared.shproj create mode 100644 Test/Test.Shared/TestData.cs create mode 100644 UI/Module/Estimation/AssemblyInfo.cs create mode 100644 UI/Module/Estimation/Controls/Dialogs/IterationOptionsDialog.xaml create mode 100644 UI/Module/Estimation/Controls/Dialogs/IterationOptionsDialog.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/DesignDigestsView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/DesignDigestsView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/DesignView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/DesignView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/HeteroscedasticExpErrorModelView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/HeteroscedasticExpErrorModelView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/HeteroscedasticPowerErrorModelView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/HeteroscedasticPowerErrorModelView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/LogNormalErrorModelView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/LogNormalErrorModelView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/NormalErrorModelView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/NormalErrorModelView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/OutputErrorModelView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/ErrorModels/OutputErrorModelView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/FitView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/FitView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/LikelihoodView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/LikelihoodView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/PosteriorView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/PosteriorView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/PriorsView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/PriorsView.xaml.cs create mode 100644 UI/Module/Estimation/Controls/Views/SimulationView.xaml create mode 100644 UI/Module/Estimation/Controls/Views/SimulationView.xaml.cs create mode 100644 UI/Module/Estimation/Design/ViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/DesignDigestsViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/DesignViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/Dialog/IterationOptionsViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/ErrorModel/OutputErrorViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/FitViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/LikelihoodViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/PosteriorViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/PriorsViewModel.cs create mode 100644 UI/Module/Estimation/Design/ViewModels/SimulationViewModel.cs create mode 100644 UI/Module/Estimation/ErrorModel/Enum.cs create mode 100644 UI/Module/Estimation/ErrorModel/ErrorModel.cs create mode 100644 UI/Module/Estimation/ErrorModel/HeteroscedasticExpErrorModel.cs create mode 100644 UI/Module/Estimation/ErrorModel/HeteroscedasticPowerErrorModel.cs create mode 100644 UI/Module/Estimation/ErrorModel/Impl/TruncNorm.cs create mode 100644 UI/Module/Estimation/ErrorModel/Interface.cs create mode 100644 UI/Module/Estimation/ErrorModel/LogNormalErrorModel.cs create mode 100644 UI/Module/Estimation/ErrorModel/NormalErrorModel.cs create mode 100644 UI/Module/Estimation/Estimation.csproj create mode 100644 UI/Module/Estimation/Logger.cs create mode 100644 UI/Module/Estimation/Model/DesignDigest.cs create mode 100644 UI/Module/Estimation/Model/EstimationDesign.cs create mode 100644 UI/Module/Estimation/Model/EstimationDesigns.cs create mode 100644 UI/Module/Estimation/Model/Extensions/CollExt.cs create mode 100644 UI/Module/Estimation/Model/Impl/EstimationDesign.Create.cs create mode 100644 UI/Module/Estimation/Model/Impl/EstimationDesign.Persist.cs create mode 100644 UI/Module/Estimation/Model/MCMC/Impl/McmcChain.cs create mode 100644 UI/Module/Estimation/Model/MCMC/McmcChain.cs create mode 100644 UI/Module/Estimation/Model/MCMC/McmcSim.cs create mode 100644 UI/Module/Estimation/Model/ModelExt.cs create mode 100644 UI/Module/Estimation/Model/ModelOutput.cs create mode 100644 UI/Module/Estimation/Model/ModelParameter.cs create mode 100644 UI/Module/Estimation/Properties/AssemblyInfo.cs create mode 100644 UI/Module/Estimation/Properties/Resources.Designer.cs create mode 100644 UI/Module/Estimation/Properties/Resources.resx create mode 100644 UI/Module/Estimation/Properties/Settings.Designer.cs create mode 100644 UI/Module/Estimation/Properties/Settings.settings create mode 100644 UI/Module/Estimation/Service.cs create mode 100644 UI/Module/Estimation/State/ChainState.cs create mode 100644 UI/Module/Estimation/State/DesignState.cs create mode 100644 UI/Module/Estimation/State/Impl/ChainState.cs create mode 100644 UI/Module/Estimation/State/Impl/ModuleState.cs create mode 100644 UI/Module/Estimation/State/Impl/PosteriorState.cs create mode 100644 UI/Module/Estimation/State/LikelihoodState.cs create mode 100644 UI/Module/Estimation/State/ModuleState.cs create mode 100644 UI/Module/Estimation/State/OutputState.cs create mode 100644 UI/Module/Estimation/State/PosteriorState.cs create mode 100644 UI/Module/Estimation/State/PriorsState.cs create mode 100644 UI/Module/Estimation/State/SimulationState.cs create mode 100644 UI/Module/Estimation/State/StateExt.cs create mode 100644 UI/Module/Estimation/View.xaml create mode 100644 UI/Module/Estimation/View.xaml.cs create mode 100644 UI/Module/Estimation/ViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/DesignDigestsViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/DesignViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Dialog/IterationOptionsViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/ErrorModel/HeteroscedasticExpErrorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/ErrorModel/HeteroscedasticPowerErrorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/ErrorModel/LogNormalErrorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/ErrorModel/NormalErrorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/ErrorModel/OutputErrorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/FitViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Impl/ChainViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Impl/DesignDigestViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Impl/ObservationsViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Impl/OutputViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Impl/PriorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/Interface.cs create mode 100644 UI/Module/Estimation/ViewModels/LikelihoodViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/PosteriorViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/PriorsViewModel.cs create mode 100644 UI/Module/Estimation/ViewModels/SimulationViewModel.cs create mode 100644 UI/Module/Estimation/app.config create mode 100644 UI/Module/Evidence/Controls/Dialogs/ImportObservationsDialog.xaml create mode 100644 UI/Module/Evidence/Controls/Dialogs/ImportObservationsDialog.xaml.cs create mode 100644 UI/Module/Evidence/Controls/Views/BrowseView.xaml create mode 100644 UI/Module/Evidence/Controls/Views/BrowseView.xaml.cs create mode 100644 UI/Module/Evidence/Controls/Views/ManageView.xaml create mode 100644 UI/Module/Evidence/Controls/Views/ManageView.xaml.cs create mode 100644 UI/Module/Evidence/Design/ViewModel.cs create mode 100644 UI/Module/Evidence/Design/ViewModels/BrowseViewModel.cs create mode 100644 UI/Module/Evidence/Design/ViewModels/Dialog/ImportObservationsViewModel.cs create mode 100644 UI/Module/Evidence/Design/ViewModels/ManageViewModel.cs create mode 100644 UI/Module/Evidence/Evidence.csproj create mode 100644 UI/Module/Evidence/Interface.cs create mode 100644 UI/Module/Evidence/Properties/AssemblyInfo.cs create mode 100644 UI/Module/Evidence/Properties/Resources.Designer.cs create mode 100644 UI/Module/Evidence/Properties/Resources.resx create mode 100644 UI/Module/Evidence/Properties/Settings.Designer.cs create mode 100644 UI/Module/Evidence/Properties/Settings.settings create mode 100644 UI/Module/Evidence/Service.cs create mode 100644 UI/Module/Evidence/State/ModuleState.cs create mode 100644 UI/Module/Evidence/View.xaml create mode 100644 UI/Module/Evidence/View.xaml.cs create mode 100644 UI/Module/Evidence/ViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/BrowseViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/Dialog/Impl/ObservationsColumnViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/Dialog/ImportObservationsViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/Impl/EvidenceSourceViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/Impl/ObservationsViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/Impl/SubjectViewModel.cs create mode 100644 UI/Module/Evidence/ViewModels/ManageViewModel.cs create mode 100644 UI/Module/Plot/Controls/ChartGrid.cs create mode 100644 UI/Module/Plot/Controls/Views/Impl/DepVarConfigView.xaml create mode 100644 UI/Module/Plot/Controls/Views/Impl/DepVarConfigView.xaml.cs create mode 100644 UI/Module/Plot/Controls/Views/Impl/ParameterBrowseView.xaml create mode 100644 UI/Module/Plot/Controls/Views/Impl/ParameterBrowseView.xaml.cs create mode 100644 UI/Module/Plot/Controls/Views/Impl/ParameterEditView.xaml create mode 100644 UI/Module/Plot/Controls/Views/Impl/ParameterEditView.xaml.cs create mode 100644 UI/Module/Plot/Controls/Views/Impl/TraceDataPlotView.xaml create mode 100644 UI/Module/Plot/Controls/Views/Impl/TraceDataPlotView.xaml.cs create mode 100644 UI/Module/Plot/Controls/Views/OutputsView.xaml create mode 100644 UI/Module/Plot/Controls/Views/OutputsView.xaml.cs create mode 100644 UI/Module/Plot/Controls/Views/ParametersView.xaml create mode 100644 UI/Module/Plot/Controls/Views/ParametersView.xaml.cs create mode 100644 UI/Module/Plot/Controls/Views/TraceView.xaml create mode 100644 UI/Module/Plot/Controls/Views/TraceView.xaml.cs create mode 100644 UI/Module/Plot/Design/Data.cs create mode 100644 UI/Module/Plot/Design/ViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/Impl/DepVarConfigViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/Impl/LogEntryViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/Impl/OutputGroupViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/Impl/TraceDataPlotViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/OutputsViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/ParameterViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/ParametersViewModel.cs create mode 100644 UI/Module/Plot/Design/ViewModels/TraceViewModel.cs create mode 100644 UI/Module/Plot/Enum.cs create mode 100644 UI/Module/Plot/Group/OutputGroup.cs create mode 100644 UI/Module/Plot/Group/OutputGroupStore.cs create mode 100644 UI/Module/Plot/Interface.cs create mode 100644 UI/Module/Plot/Logger.cs create mode 100644 UI/Module/Plot/Plot.csproj create mode 100644 UI/Module/Plot/Properties/AssemblyInfo.cs create mode 100644 UI/Module/Plot/Properties/Resources.Designer.cs create mode 100644 UI/Module/Plot/Properties/Resources.resx create mode 100644 UI/Module/Plot/Properties/Settings.Designer.cs create mode 100644 UI/Module/Plot/Properties/Settings.settings create mode 100644 UI/Module/Plot/Service.cs create mode 100644 UI/Module/Plot/State/DepVarConfigState.cs create mode 100644 UI/Module/Plot/State/Impl/ModuleState.cs create mode 100644 UI/Module/Plot/State/Impl/ModuleStateDTO.cs create mode 100644 UI/Module/Plot/State/ModuleState.cs create mode 100644 UI/Module/Plot/State/ParameterEditState.cs create mode 100644 UI/Module/Plot/State/TraceDataPlotState.cs create mode 100644 UI/Module/Plot/State/TraceState.cs create mode 100644 UI/Module/Plot/View.xaml create mode 100644 UI/Module/Plot/View.xaml.cs create mode 100644 UI/Module/Plot/ViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/DepVarConfigViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/LogEntryViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/OutputGroupViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/ParameterViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/Parameters.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/SelectableItemViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/Impl/TraceDataPlotViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/OutputsViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/ParametersViewModel.cs create mode 100644 UI/Module/Plot/ViewModels/TraceViewModel.cs create mode 100644 UI/Module/Sampling/Controls/Views/DesignDigestsView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/DesignDigestsView.xaml.cs create mode 100644 UI/Module/Sampling/Controls/Views/DesignView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/DesignView.xaml.cs create mode 100644 UI/Module/Sampling/Controls/Views/Impl/NoDesignActivityView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/Impl/NoDesignActivityView.xaml.cs create mode 100644 UI/Module/Sampling/Controls/Views/Impl/OutputsDesignActivityView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/Impl/OutputsDesignActivityView.xaml.cs create mode 100644 UI/Module/Sampling/Controls/Views/Impl/ParameterSamplingView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/Impl/ParameterSamplingView.xaml.cs create mode 100644 UI/Module/Sampling/Controls/Views/Impl/SamplesDesignActivityView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/Impl/SamplesDesignActivityView.xaml.cs create mode 100644 UI/Module/Sampling/Controls/Views/ParametersView.xaml create mode 100644 UI/Module/Sampling/Controls/Views/ParametersView.xaml.cs create mode 100644 UI/Module/Sampling/Design/Model/Data.cs create mode 100644 UI/Module/Sampling/Design/ViewModel.cs create mode 100644 UI/Module/Sampling/Design/ViewModels/DesignDigestsViewModel.cs create mode 100644 UI/Module/Sampling/Design/ViewModels/DesignViewModel.cs create mode 100644 UI/Module/Sampling/Design/ViewModels/ParametersViewModel.cs create mode 100644 UI/Module/Sampling/Interface.cs create mode 100644 UI/Module/Sampling/Logger.cs create mode 100644 UI/Module/Sampling/Model/DesignDigest.cs create mode 100644 UI/Module/Sampling/Model/DesignParameter.cs create mode 100644 UI/Module/Sampling/Model/Impl/SamplingDesign.cs create mode 100644 UI/Module/Sampling/Model/ModelExt.cs create mode 100644 UI/Module/Sampling/Model/SamplingDesign.cs create mode 100644 UI/Module/Sampling/Model/SamplingDesigns.cs create mode 100644 UI/Module/Sampling/Properties/AssemblyInfo.cs create mode 100644 UI/Module/Sampling/Properties/Resources.Designer.cs create mode 100644 UI/Module/Sampling/Properties/Resources.resx create mode 100644 UI/Module/Sampling/Properties/Settings.Designer.cs create mode 100644 UI/Module/Sampling/Properties/Settings.settings create mode 100644 UI/Module/Sampling/Sampling.csproj create mode 100644 UI/Module/Sampling/Service.cs create mode 100644 UI/Module/Sampling/State/DesignState.cs create mode 100644 UI/Module/Sampling/State/Impl/ModuleState.cs create mode 100644 UI/Module/Sampling/State/ModuleState.cs create mode 100644 UI/Module/Sampling/State/ParametersState.cs create mode 100644 UI/Module/Sampling/View.xaml create mode 100644 UI/Module/Sampling/View.xaml.cs create mode 100644 UI/Module/Sampling/ViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/DesignDigestsViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/DesignViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/Design/DesignActivityViewModelBase.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/Design/Interface.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/Design/NoDesignActivityViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/Design/OutputsDesignActivityViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/Design/SamplesDesignActivityViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/DesignDigestViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/ParameterSamplingViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/Impl/ParameterViewModel.cs create mode 100644 UI/Module/Sampling/ViewModels/ParametersViewModel.cs create mode 100644 UI/Module/Sensitivity/Controls/Dialogs/ChartOptionsDialog.xaml create mode 100644 UI/Module/Sensitivity/Controls/Dialogs/ChartOptionsDialog.xaml.cs create mode 100644 UI/Module/Sensitivity/Controls/Views/DesignDigestsView.xaml create mode 100644 UI/Module/Sensitivity/Controls/Views/DesignDigestsView.xaml.cs create mode 100644 UI/Module/Sensitivity/Controls/Views/DesignView.xaml create mode 100644 UI/Module/Sensitivity/Controls/Views/DesignView.xaml.cs create mode 100644 UI/Module/Sensitivity/Controls/Views/EffectsView.xaml create mode 100644 UI/Module/Sensitivity/Controls/Views/EffectsView.xaml.cs create mode 100644 UI/Module/Sensitivity/Controls/Views/ParametersView.xaml create mode 100644 UI/Module/Sensitivity/Controls/Views/ParametersView.xaml.cs create mode 100644 UI/Module/Sensitivity/Controls/Views/VarianceView.xaml create mode 100644 UI/Module/Sensitivity/Controls/Views/VarianceView.xaml.cs create mode 100644 UI/Module/Sensitivity/Design/Model/Data.cs create mode 100644 UI/Module/Sensitivity/Design/ViewModel.cs create mode 100644 UI/Module/Sensitivity/Design/ViewModels/DesignDigestsViewModel.cs create mode 100644 UI/Module/Sensitivity/Design/ViewModels/DesignViewModel.cs create mode 100644 UI/Module/Sensitivity/Design/ViewModels/EffectsViewModel.cs create mode 100644 UI/Module/Sensitivity/Design/ViewModels/ParametersViewModel.cs create mode 100644 UI/Module/Sensitivity/Design/ViewModels/VarianceViewModel.cs create mode 100644 UI/Module/Sensitivity/Logger.cs create mode 100644 UI/Module/Sensitivity/Model/DesignDigest.cs create mode 100644 UI/Module/Sensitivity/Model/DesignParameter.cs create mode 100644 UI/Module/Sensitivity/Model/Impl/SensitivityDesign.Create.cs create mode 100644 UI/Module/Sensitivity/Model/Impl/SensitivityDesign.Measure.cs create mode 100644 UI/Module/Sensitivity/Model/Impl/SensitivityDesign.persist.cs create mode 100644 UI/Module/Sensitivity/Model/ModelExt.cs create mode 100644 UI/Module/Sensitivity/Model/SensitivityDesign.cs create mode 100644 UI/Module/Sensitivity/Model/SensitivityDesigns.cs create mode 100644 UI/Module/Sensitivity/Properties/AssemblyInfo.cs create mode 100644 UI/Module/Sensitivity/Properties/Resources.Designer.cs create mode 100644 UI/Module/Sensitivity/Properties/Resources.resx create mode 100644 UI/Module/Sensitivity/Properties/Settings.Designer.cs create mode 100644 UI/Module/Sensitivity/Properties/Settings.settings create mode 100644 UI/Module/Sensitivity/Sensitivity.csproj create mode 100644 UI/Module/Sensitivity/Service.cs create mode 100644 UI/Module/Sensitivity/State/DesignState.cs create mode 100644 UI/Module/Sensitivity/State/Impl/ModuleState.cs create mode 100644 UI/Module/Sensitivity/State/LowryState.cs create mode 100644 UI/Module/Sensitivity/State/MeasuresState.cs create mode 100644 UI/Module/Sensitivity/State/ModuleState.cs create mode 100644 UI/Module/Sensitivity/State/ParametersState.cs create mode 100644 UI/Module/Sensitivity/State/TraceState.cs create mode 100644 UI/Module/Sensitivity/View.xaml create mode 100644 UI/Module/Sensitivity/View.xaml.cs create mode 100644 UI/Module/Sensitivity/ViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/DesignDigestsViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/DesignViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Dialog/ChartOptionsViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/EffectsViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Enum.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/DesignDigestViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/DesignViewModel.static.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/LowryPlotData.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/LowryViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/MeasuresOps.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/OxyColorData.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/ParameterViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Impl/TraceViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/Interface.cs create mode 100644 UI/Module/Sensitivity/ViewModels/ParametersViewModel.cs create mode 100644 UI/Module/Sensitivity/ViewModels/VarianceViewModel.cs create mode 100644 UI/Module/deps/3rd/OxyPlot.Wpf.dll create mode 100644 UI/Module/deps/3rd/OxyPlot.Wpf.pdb create mode 100644 UI/Module/deps/3rd/OxyPlot.dll create mode 100644 UI/Module/deps/3rd/OxyPlot.pdb create mode 100644 UI/RVisUI.AppInf/Controls/Media/TiffExporter.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/BetaDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/BetaDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/BetaScaledDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/BetaScaledDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/GammaDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/GammaDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/InvariantDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/InvariantDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/InverseGammaDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/InverseGammaDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/LogNormalDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/LogNormalDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/LogUniformDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/LogUniformDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/NormalDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/NormalDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/StudentTDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/StudentTDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/UniformDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/Distributions/UniformDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Controls/Views/ParameterDistributionView.xaml create mode 100644 UI/RVisUI.AppInf/Controls/Views/ParameterDistributionView.xaml.cs create mode 100644 UI/RVisUI.AppInf/Design/Model/AppService.cs create mode 100644 UI/RVisUI.AppInf/Design/Model/AppSettings.cs create mode 100644 UI/RVisUI.AppInf/Design/ViewModels/ParameterDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/Extensions/ParameterStateExt.cs create mode 100644 UI/RVisUI.AppInf/Extensions/PlotExt.cs create mode 100644 UI/RVisUI.AppInf/Model/State/ParameterState.cs create mode 100644 UI/RVisUI.AppInf/Properties/AssemblyInfo.cs create mode 100644 UI/RVisUI.AppInf/Properties/Resources.Designer.cs create mode 100644 UI/RVisUI.AppInf/Properties/Resources.resx create mode 100644 UI/RVisUI.AppInf/Properties/Settings.Designer.cs create mode 100644 UI/RVisUI.AppInf/Properties/Settings.settings create mode 100644 UI/RVisUI.AppInf/RVisUI.AppInf.csproj create mode 100644 UI/RVisUI.AppInf/Resources/HelpButton.xaml create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/BetaDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/BetaScaledDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/GammaDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/Impl/DistributionLineSeries.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/Impl/DistributionViewModelBase.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/Impl/Interface.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/InvariantDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/InverseGammaDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/LogNormalDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/LogUniformDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/NormalDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/StudentTDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Distributions/UniformDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/Interface.cs create mode 100644 UI/RVisUI.AppInf/ViewModels/ParameterDistributionViewModel.cs create mode 100644 UI/RVisUI.AppInf/app.config create mode 100644 UI/RVisUI.Ioc/Design/AppModule.cs create mode 100644 UI/RVisUI.Ioc/Design/Infrastructure/NinjectBootstrapper.cs create mode 100644 UI/RVisUI.Ioc/Design/ViewModelModule.cs create mode 100644 UI/RVisUI.Ioc/Infrastructure/NinjectBootstrapper.cs create mode 100644 UI/RVisUI.Ioc/Infrastructure/NinjectServiceLocator.cs create mode 100644 UI/RVisUI.Ioc/Interface.cs create mode 100644 UI/RVisUI.Ioc/RVisUI.Ioc.csproj create mode 100644 UI/RVisUI.Ioc/ServiceModule.cs create mode 100644 UI/RVisUI.Ioc/ViewModelLocator.cs create mode 100644 UI/RVisUI.Ioc/ViewModelModule.cs create mode 100644 UI/RVisUI.Model/AssemblyInfo.cs create mode 100644 UI/RVisUI.Model/Attribute.cs create mode 100644 UI/RVisUI.Model/Enum.cs create mode 100644 UI/RVisUI.Model/EventArgs.cs create mode 100644 UI/RVisUI.Model/Extensions/FxExt.cs create mode 100644 UI/RVisUI.Model/Extensions/ModelExt.cs create mode 100644 UI/RVisUI.Model/Extensions/ModuleExt.cs create mode 100644 UI/RVisUI.Model/Extensions/SharedStateExt.cs create mode 100644 UI/RVisUI.Model/Interface.cs create mode 100644 UI/RVisUI.Model/Logger.cs create mode 100644 UI/RVisUI.Model/Module/ModuleInfo.cs create mode 100644 UI/RVisUI.Model/Mvvm/ViewModelBase.cs create mode 100644 UI/RVisUI.Model/RVisUI.Model.csproj create mode 100644 UI/RVisUI.Model/ReactiveSafeInvoke.cs create mode 100644 UI/RVisUI.Model/SharedState/SimElementSharedState.cs create mode 100644 UI/RVisUI.Model/SharedState/SimObservationsSharedState.cs create mode 100644 UI/RVisUI.Model/SharedState/SimParameterSharedState.cs create mode 100644 UI/RVisUI.Model/SharedState/SimSharedState.cs create mode 100644 UI/RVisUI.Model/SharedState/SimSharedStateBuilder.cs create mode 100644 UI/RVisUI.Mvvm/Design/Dialog/ChangeDescriptionUnitViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/Dialog/CommonConfigurationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/Dialog/ImportExecViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/Dialog/ImportTmplViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/HomeViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/Impl/SharedStateViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/Impl/SimulationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/ImportSimulationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/ModuleNotSupportedViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/SelectSimulationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Design/SimulationHomeViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Dialog/ChangeDescriptionUnitViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Dialog/CommonConfigurationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Dialog/Impl/ModuleViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Dialog/ImportExecViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Dialog/ImportTmplViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Dialog/SelectExecViewModel.cs create mode 100644 UI/RVisUI.Mvvm/FailedStartUpViewModel.cs create mode 100644 UI/RVisUI.Mvvm/HomeViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Impl/ElementCandidateViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Impl/ParameterCandidateViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Impl/SharedStateViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Impl/SimulationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/ImportSimulationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/Infrastructure/Interface.cs create mode 100644 UI/RVisUI.Mvvm/Infrastructure/Logger.cs create mode 100644 UI/RVisUI.Mvvm/LibraryViewModel.cs create mode 100644 UI/RVisUI.Mvvm/ModuleNotSupportedViewModel.cs create mode 100644 UI/RVisUI.Mvvm/RVisUI.Mvvm.csproj create mode 100644 UI/RVisUI.Mvvm/SelectSimulationViewModel.cs create mode 100644 UI/RVisUI.Mvvm/SimulationHomeViewModel.cs create mode 100644 UI/RVisUI.WPF/Adorners/AdornedControl.cs create mode 100644 UI/RVisUI.WPF/Adorners/Enum.cs create mode 100644 UI/RVisUI.WPF/Adorners/FrameworkElementAdorner.cs create mode 100644 UI/RVisUI.WPF/Behavior/ListBoxSelectionBehavior.cs create mode 100644 UI/RVisUI.WPF/Converter/BrushToHexConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/ColorToBrushConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/ElementTypeConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/EnumMatchToBooleanConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/GridLengthConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/InverseBooleanConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/KeyEventArgsConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/SigFigsConverter.cs create mode 100644 UI/RVisUI.WPF/Converter/UpperCaseConverter.cs create mode 100644 UI/RVisUI.WPF/DepProps/Behaviour.cs create mode 100644 UI/RVisUI.WPF/DepProps/Content.cs create mode 100644 UI/RVisUI.WPF/DepProps/Impl/DataPipe.cs create mode 100644 UI/RVisUI.WPF/DepProps/Impl/DataPipeCollection.cs create mode 100644 UI/RVisUI.WPF/DepProps/Layout.cs create mode 100644 UI/RVisUI.WPF/Interactivity/EventArgsCommandAction.cs create mode 100644 UI/RVisUI.WPF/Properties/AssemblyInfo.cs create mode 100644 UI/RVisUI.WPF/Properties/Resources.Designer.cs create mode 100644 UI/RVisUI.WPF/Properties/Resources.resx create mode 100644 UI/RVisUI.WPF/Properties/Settings.Designer.cs create mode 100644 UI/RVisUI.WPF/Properties/Settings.settings create mode 100644 UI/RVisUI.WPF/RVisUI.Wpf.csproj create mode 100644 UI/RVisUI.WPF/Resources/DesignModeResourceDictionary.cs create mode 100644 UI/RVisUI/App.config create mode 100644 UI/RVisUI/App.xaml create mode 100644 UI/RVisUI/App.xaml.cs create mode 100644 UI/RVisUI/AppImpl/App.Diagnostics.cs create mode 100644 UI/RVisUI/AppImpl/App.Startup.cs create mode 100644 UI/RVisUI/AppImpl/App.Window.cs create mode 100644 UI/RVisUI/Controls/ActiveViewTemplateSelector.cs create mode 100644 UI/RVisUI/Controls/AppSettingsFlyout.xaml create mode 100644 UI/RVisUI/Controls/AppSettingsFlyout.xaml.cs create mode 100644 UI/RVisUI/Controls/BusyOverlay.xaml create mode 100644 UI/RVisUI/Controls/BusyOverlay.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/AskYesNoDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/AskYesNoDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/ChangeDescriptionUnitDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/ChangeDescriptionUnitDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/CommonConfigurationDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/CommonConfigurationDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/ConfigureModulesDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/ConfigureModulesDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/ImportExecDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/ImportExecDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/ImportTmplDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/ImportTmplDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/NotifyDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/NotifyDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Dialogs/SelectExecDialog.xaml create mode 100644 UI/RVisUI/Controls/Dialogs/SelectExecDialog.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/FailedStartUpView.xaml create mode 100644 UI/RVisUI/Controls/Views/FailedStartUpView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/HomeView.xaml create mode 100644 UI/RVisUI/Controls/Views/HomeView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/Impl/PaletteSelector.xaml create mode 100644 UI/RVisUI/Controls/Views/Impl/PaletteSelector.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/Impl/SharedStateView.xaml create mode 100644 UI/RVisUI/Controls/Views/Impl/SharedStateView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/ImportSimulationView.xaml create mode 100644 UI/RVisUI/Controls/Views/ImportSimulationView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/LibraryView.xaml create mode 100644 UI/RVisUI/Controls/Views/LibraryView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/ModuleNotSupportedView.xaml create mode 100644 UI/RVisUI/Controls/Views/ModuleNotSupportedView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/SelectSimulationView.xaml create mode 100644 UI/RVisUI/Controls/Views/SelectSimulationView.xaml.cs create mode 100644 UI/RVisUI/Controls/Views/SimulationHomeView.xaml create mode 100644 UI/RVisUI/Controls/Views/SimulationHomeView.xaml.cs create mode 100644 UI/RVisUI/DepProps/Content/Content.cs create mode 100644 UI/RVisUI/DepProps/Layout/Layout.cs create mode 100644 UI/RVisUI/Extensions/SettingsExt.cs create mode 100644 UI/RVisUI/Interop/SafeNativeMethods.cs create mode 100644 UI/RVisUI/Ioc/AppModule.cs create mode 100644 UI/RVisUI/Ioc/AppService.cs create mode 100644 UI/RVisUI/Ioc/AppSettings.cs create mode 100644 UI/RVisUI/Ioc/AppState.cs create mode 100644 UI/RVisUI/Ioc/Impl/AppState.Lifecycle.cs create mode 100644 UI/RVisUI/Ioc/Impl/AppState.Module.cs create mode 100644 UI/RVisUI/Ioc/Impl/AppState.Sim.cs create mode 100644 UI/RVisUI/Ioc/Impl/AppState.UI.cs create mode 100644 UI/RVisUI/Ioc/Mvvm/AppSettingsViewModel.cs create mode 100644 UI/RVisUI/Ioc/Mvvm/Enum.cs create mode 100644 UI/RVisUI/Ioc/Mvvm/Impl/HueViewModel.cs create mode 100644 UI/RVisUI/Ioc/Mvvm/Impl/SwatchViewModel.cs create mode 100644 UI/RVisUI/Ioc/Mvvm/Interface.cs create mode 100644 UI/RVisUI/Ioc/Mvvm/ModuleConfigViewModel.cs create mode 100644 UI/RVisUI/MainWindow.xaml create mode 100644 UI/RVisUI/MainWindow.xaml.cs create mode 100644 UI/RVisUI/Properties/AssemblyInfo.cs create mode 100644 UI/RVisUI/Properties/Resources.Designer.cs create mode 100644 UI/RVisUI/Properties/Resources.resx create mode 100644 UI/RVisUI/Properties/Settings.Designer.cs create mode 100644 UI/RVisUI/Properties/Settings.settings create mode 100644 UI/RVisUI/RVisUI.csproj create mode 100644 UI/RVisUI/Resources/app.ico create mode 100644 Version/VersionInfo.cs create mode 100644 WinR/RVis.Client/NativeMethods.cs create mode 100644 WinR/RVis.Client/Properties/AssemblyInfo.cs create mode 100644 WinR/RVis.Client/RVis.Client.csproj create mode 100644 WinR/RVis.Client/RVisClientProxy.Callback.cs create mode 100644 WinR/RVis.Client/RVisClientProxy.Source.cs create mode 100644 WinR/RVis.Client/RVisServer.RVisClientProxy.cs create mode 100644 WinR/RVis.Client/RVisServer.cs create mode 100644 WinR/RVis.ROps/Impl/ROpsApi.Evaluate.cs create mode 100644 WinR/RVis.ROps/Impl/ROpsApi.Input.cs create mode 100644 WinR/RVis.ROps/Impl/ROpsApi.Inspect.cs create mode 100644 WinR/RVis.ROps/Impl/ROpsApi.Retrieve.cs create mode 100644 WinR/RVis.ROps/Properties/AssemblyInfo.cs create mode 100644 WinR/RVis.ROps/Properties/Resources.Designer.cs create mode 100644 WinR/RVis.ROps/Properties/Resources.resx create mode 100644 WinR/RVis.ROps/ROpsApi.Code.cs create mode 100644 WinR/RVis.ROps/ROpsApi.Persistence.cs create mode 100644 WinR/RVis.ROps/ROpsApi.Sim.cs create mode 100644 WinR/RVis.ROps/ROpsApi.cs create mode 100644 WinR/RVis.ROps/RVis.ROps.csproj create mode 100644 WinR/RVis.Server/App.config create mode 100644 WinR/RVis.Server/Program.cs create mode 100644 WinR/RVis.Server/Properties/AssemblyInfo.cs create mode 100644 WinR/RVis.Server/RVis.Server.csproj create mode 100644 WinR/RVis.Server/RVisService.Duplex.cs create mode 100644 WinR/RVis.Server/RVisService.Source.cs create mode 100644 WinR/RVis.Server/RVisService.cs create mode 100644 WinR/WinR.Shared/ServiceHelper.cs create mode 100644 WinR/WinR.Shared/WinR.Shared.projitems create mode 100644 WinR/WinR.Shared/WinR.Shared.shproj create mode 100644 azure-pipelines.yml create mode 100644 clean.bat create mode 100644 clean_x64.bat create mode 100644 prepare_archive.bat create mode 100644 prepare_archive_x64.bat create mode 100644 prepare_release.bat create mode 100644 release_notes.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 3e759b7..1490f28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -24,14 +22,11 @@ bld/ [Oo]bj/ [Ll]og/ -# Visual Studio 2015/2017 cache/options directory +# Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ -# Visual Studio 2017 auto generated files -Generated\ Files/ - # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* @@ -45,29 +40,19 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json - -# StyleCop -StyleCopReport.xml -# Files built by Visual Studio *_i.c *_p.c *_i.h *.ilk *.meta *.obj -*.iobj *.pch *.pdb -*.ipdb *.pgc *.pgd *.rsp @@ -105,9 +90,6 @@ ipch/ *.vspx *.sap -# Visual Studio Trace Files -*.e2e - # TFS 2012 Local Workspace $tf/ @@ -128,14 +110,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -167,9 +141,9 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted -*.pubxml +#*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to @@ -180,12 +154,12 @@ PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* +**/packages/* # except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ +!**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -202,7 +176,6 @@ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt -*.appx # Visual Studio cache files # files ending in .cache can be ignored @@ -219,12 +192,9 @@ ClientBin/ *.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ @@ -239,19 +209,15 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings -*.rptproj.rsuser # Microsoft Fakes FakesAssemblies/ @@ -261,7 +227,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ # Visual Studio 6 build log *.plg @@ -269,9 +234,6 @@ node_modules/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -298,33 +260,8 @@ paket-files/ __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ +UI/Module/deps +rvis +rvisx64 +rvis.zip +rvisx64.zip diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..ed6cad3 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + HSE + RVis + Copyright © HSE 2019 + 0.6.11068.0 + 0.6.11068.0 + + diff --git a/RVis.Base/Check.cs b/RVis.Base/Check.cs new file mode 100644 index 0000000..4b223ee --- /dev/null +++ b/RVis.Base/Check.cs @@ -0,0 +1,597 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using static RVis.Base.Constant; +using static RVis.Base.Properties.Resources; +using static System.Math; +using static System.String; + +namespace RVis.Base +{ + /// + /// Code contracts + /// + /// + public static class Check + { + #region TrueFalse + + /// + /// Throws an InvalidOperationException if the condition is not true. + /// + /// Something that should be true. + [DebuggerStepThrough] + public static void RequireTrue(bool condition) => + RequireTrue(condition, ERR_NOT_TRUE); + + /// + /// Throws an InvalidOperationException if the condition is not true. + /// + /// Something that should be true. + /// Message for the exception if the condition is not true. + [DebuggerStepThrough] + public static void RequireTrue(bool condition, [Localizable(false)] string message) + { + if (!condition) + { + throw new InvalidOperationException(message); + } + } + + /// + /// Throws an InvalidOperationException if the condition is not false. + /// + /// Something that should be false. + [DebuggerStepThrough] + public static void RequireFalse(bool condition) => + RequireFalse(condition, ERR_NOT_FALSE); + + /// + /// Throws an InvalidOperationException if the condition is not false. + /// + /// Something that should be false. + /// Message for the exception if the condition is not false. + [DebuggerStepThrough] + public static void RequireFalse(bool condition, [Localizable(false)] string message) => + RequireTrue(!condition, message); + + #endregion + + #region Null + + /// + /// Throws an ArgumentException if the object is not null. + /// + /// Something that should be null. + [DebuggerStepThrough] + public static void RequireNull(object o) => + RequireNull(o, ERR_NOT_NULL); + + /// + /// Throws an ArgumentException if the object is not null. + /// + /// Something that should be null. + /// Message for the exception if the object is not null. + [DebuggerStepThrough] + public static void RequireNull(object o, [Localizable(false)] string message) + { + if (o != null) + { + throw new ArgumentException(message); + } + } + + /// + /// Throws an ArgumentNullException if the string is null or empty. + /// + /// A string should that not be null or empty. + [DebuggerStepThrough] + public static void RequireNullEmptyWhiteSpace(string s) => + RequireNullEmptyWhiteSpace(s, ERR_STRING_NOT_NULL_OR_EMPTY); + + /// + /// Throws an ArgumentNullException if the string is null or empty. + /// + /// A string should that not be null or empty. + /// Message for the exception if the string is null or empty. + [DebuggerStepThrough] + public static void RequireNullEmptyWhiteSpace(string s, [Localizable(false)] string message) + { + if (!IsNullOrWhiteSpace(s)) + { + throw new ArgumentNullException(message); + } + } + + #endregion + + #region NotNull + + /// + /// Throws an ArgumentNullException if the object is null. + /// + /// Something that should not be null. + [DebuggerStepThrough] + public static void RequireNotNull(object o) => + RequireNotNull(o, ERR_NULL); + + /// + /// Throws an ArgumentNullException if the object is null. + /// + /// Something that should not be null. + /// Message for the exception if the object is null. + [DebuggerStepThrough] + public static void RequireNotNull(object o, [Localizable(false)] string message) + { + if (o == null) + { + throw new ArgumentNullException(message); + } + } + + /// + /// Throws an ArgumentNullException if the string is null or empty. + /// + /// A string should that not be null or empty. + [DebuggerStepThrough] + public static void RequireNotNullEmptyWhiteSpace(string s) => + RequireNotNullEmptyWhiteSpace(s, ERR_STRING_NULL_OR_EMPTY); + + /// + /// Throws an ArgumentNullException if the string is null or empty. + /// + /// A string should that not be null or empty. + /// Message for the exception if the string is null or empty. + [DebuggerStepThrough] + public static void RequireNotNullEmptyWhiteSpace(string s, [Localizable(false)] string message) + { + if (IsNullOrWhiteSpace(s)) + { + throw new ArgumentNullException(message); + } + } + + #endregion + + #region EqualNotEqual + + /// + /// Throws an ArgumentException if the objects are not equal. + /// + /// An object. + /// Another object that should be equal to the first. + [DebuggerStepThrough] + public static void RequireEqual(object o1, object o2) => + RequireEqual(o1, o2, ERR_NOT_EQUAL); + + /// + /// Throws an ArgumentException if the objects are not equal. + /// + /// An object. + /// Another object that should be equal to the first. + /// Message for the exception if the objects are not equal. + [DebuggerStepThrough] + public static void RequireEqual(object o1, object o2, [Localizable(false)] string message) + { + if (!o1.Equals(o2)) + { + throw new ArgumentException(message); + } + } + + /// + /// Throws an ArgumentException if the objects are equal. + /// + /// An object. + /// Another object that shouldn't be equal to the first. + [DebuggerStepThrough] + public static void RequireNotEqual(object o1, object o2) => + RequireNotEqual(o1, o2, ERR_EQUAL); + + /// + /// Throws an ArgumentException if the objects are equal. + /// + /// An object. + /// Another object that shouldn't be equal to the first. + /// Message for the exception if the objects are equal. + [DebuggerStepThrough] + public static void RequireNotEqual(object o1, object o2, [Localizable(false)] string message) + { + if (o1.Equals(o2)) + { + throw new ArgumentException(message); + } + } + + /// + /// Throws an ArgumentException if two numbers are not equal, within a specified tolerance (default = 1.5e-8). + /// + /// A number. + /// Another number that should be more or less equal to the first. + [DebuggerStepThrough] + public static void RequireAlmostEqual(double x, double y) => + RequireAlmostEqual(x, y, TOLERANCE, ERR_NOT_ALMOST_EQUAL); + + /// + /// Throws an ArgumentException if two numbers are not equal, within a specified tolerance (default = 1.5e-8). + /// + /// A number. + /// Another number that should be more or less equal to the first. + /// Message for the exception if the two numbers are not equal. + [DebuggerStepThrough] + public static void RequireAlmostEqual(double x, double y, [Localizable(false)] string message) => + RequireAlmostEqual(x, y, TOLERANCE, message); + + /// + /// Throws an ArgumentException if two numbers are not equal, within a specified tolerance (default = 1.5e-8). + /// + /// A number. + /// Another number that should be more or less equal to the first. + /// Tolerance for equality. + [DebuggerStepThrough] + public static void RequireAlmostEqual(double x, double y, double eps) => + RequireAlmostEqual(x, y, eps, ERR_NOT_ALMOST_EQUAL); + + /// + /// Throws an ArgumentException if two numbers are not equal, within a specified tolerance (default = 1.5e-8). + /// + /// A number. + /// Another number that should be more or less equal to the first. + /// Tolerance for equality. + /// Message for the exception if the two numbers are not equal. + [DebuggerStepThrough] + public static void RequireAlmostEqual(double x, double y, double eps, [Localizable(false)] string message) + { + if (Abs(x - y) >= eps) + { + throw new ArgumentException(message); + } + } + + #endregion + + #region IsInRange + + /// + /// Throws an ArgumentOutOfRangeException if a T is not in a specified range. + /// + /// A T that should be in range. + /// The lower bound of the range. + /// The upper bound of the range. + [DebuggerStepThrough] + public static void RequireInRange(T x, T lowerBound, T upperBound) where T : struct, IComparable => + RequireInRange(x, lowerBound, upperBound, ERR_BELOW_RANGE, ERR_ABOVE_RANGE); + + /// + /// Throws an ArgumentOutOfRangeException if a T is not in a specified range. + /// + /// A T that should be in range. + /// The lower bound of the range. + /// The upper bound of the range. + /// Message for the exception if the T is too low. + /// Message for the exception if the T is too high. + [DebuggerStepThrough] + public static void RequireInRange(T x, T lowerBound, T upperBound, string messageLower, string messageUpper) where T : struct, IComparable + { + if (x.CompareTo(lowerBound) < 0) + { + throw new ArgumentOutOfRangeException(messageLower); + } + if (x.CompareTo(upperBound) > 0) + { + throw new ArgumentOutOfRangeException(messageUpper); + } + } + + /// + /// Throws an ArgumentOutOfRangeException if a number is not strictly in a specified range. + /// + /// A number that should be in range. + /// The lower bound of the range. + /// The upper bound of the range. + [DebuggerStepThrough] + public static void RequireInRange(double x, double lowerBound, double upperBound) => + RequireInRange(x, lowerBound, upperBound, DefaultLowerBoundStrictness, DefaultUpperBoundStrictness); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not strictly in a specified range. + /// + /// A number that should be in range. + /// The lower bound of the range. + /// The upper bound of the range. + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequireInRange(double x, double lowerBound, double upperBound, string messageNaN, string messageLower, string messageUpper) => + RequireInRange(x, lowerBound, upperBound, DefaultLowerBoundStrictness, DefaultUpperBoundStrictness, messageNaN, messageLower, messageUpper); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not in a specified range. + /// + /// A number that should be in range. + /// The lower bound of the range. + /// The upper bound of the range. + /// Whether or not the lower bound is strict (not included in the range). + /// Whether or not the upper bound is strict (not included in the range). + [DebuggerStepThrough] + public static void RequireInRange(double x, double lowerBound, double upperBound, bool isLowerBoundStrict, bool isUpperBoundStrict) => + RequireInRange(x, lowerBound, upperBound, isLowerBoundStrict, isUpperBoundStrict, ERR_NAN, ERR_BELOW_RANGE, ERR_ABOVE_RANGE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not in a specified range. + /// + /// A number that should be in range. + /// The lower bound of the range. + /// The upper bound of the range. + /// Whether or not the lower bound is strict (not included in the range). + /// Whether or not the upper bound is strict (not included in the range). + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequireInRange(double x, double lowerBound, double upperBound, bool isLowerBoundStrict, bool isUpperBoundStrict, string messageNaN, string messageLower, string messageUpper) + { + if (double.IsNaN(x)) + { + throw new ArgumentOutOfRangeException(messageNaN); + } + if (x < lowerBound || (isLowerBoundStrict && x == lowerBound)) + { + throw new ArgumentOutOfRangeException(messageLower); + } + if (x > upperBound || (isUpperBoundStrict && x == upperBound)) + { + throw new ArgumentOutOfRangeException(messageUpper); + } + } + + #endregion + + #region IsFraction + + /// + /// Throws an error if a number is not strictly between zero and one. + /// + /// A number that should be in range. + [DebuggerStepThrough] + public static void RequireFraction(double x) => + RequireFraction(x, DefaultLowerBoundStrictness, DefaultUpperBoundStrictness, ERR_NAN, ERR_BELOW_RANGE, ERR_ABOVE_RANGE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not strictly between zero and one. + /// + /// A number that should be in range. + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequireFraction(double x, string messageNaN, string messageLower, string messageUpper) => + RequireFraction(x, DefaultLowerBoundStrictness, DefaultUpperBoundStrictness, messageNaN, messageLower, messageUpper); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not between zero and one. + /// + /// A number that should be in range. + /// Whether or not the lower bound is strict (not included in the range). + /// Whether or not the upper bound is strict (not included in the range). + [DebuggerStepThrough] + public static void RequireFraction(double x, bool isLowerBoundStrict, bool isUpperBoundStrict) => + RequireFraction(x, isLowerBoundStrict, isUpperBoundStrict, ERR_NAN, ERR_BELOW_RANGE, ERR_ABOVE_RANGE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not between zero and one. + /// + /// A number that should be in range. + /// Whether or not the lower bound is strict (not included in the range). + /// Whether or not the upper bound is strict (not included in the range). + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequireFraction(double x, bool isLowerBoundStrict, bool isUpperBoundStrict, string messageNaN, string messageLower, string messageUpper) => + RequireInRange(x, 0.0, 1.0, isLowerBoundStrict, isUpperBoundStrict, messageNaN, messageLower, messageUpper); + + #endregion + + #region IsPercentage + + /// + /// Throws an ArgumentOutOfRangeException if a number is not strictly between zero and one hundred. + /// + /// A number that should be in range. + [DebuggerStepThrough] + public static void RequirePercentage(double x) => + RequirePercentage(x, DefaultLowerBoundStrictness, DefaultUpperBoundStrictness, ERR_NAN, ERR_BELOW_RANGE, ERR_ABOVE_RANGE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not strictly between zero and one hundred. + /// + /// A number that should be in range. + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequirePercentage(double x, string messageNaN, string messageLower, string messageUpper) => + RequirePercentage(x, DefaultLowerBoundStrictness, DefaultUpperBoundStrictness, messageNaN, messageLower, messageUpper); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not between zero and one hundred. + /// + /// A number that should be in range. + /// Whether or not the lower bound is strict (not included in the range). + /// Whether or not the upper bound is strict (not included in the range). + [DebuggerStepThrough] + public static void RequirePercentage(double x, bool isLowerBoundStrict, bool isUpperBoundStrict) => + RequirePercentage(x, isLowerBoundStrict, isUpperBoundStrict, ERR_NAN, ERR_BELOW_RANGE, ERR_ABOVE_RANGE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not between zero and one hundred. + /// + /// A number that should be in range. + /// Whether or not the lower bound is strict (not included in the range). + /// Whether or not the upper bound is strict (not included in the range). + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequirePercentage(double x, bool isLowerBoundStrict, bool isUpperBoundStrict, string messageNaN, string messageLower, string messageUpper) => + RequireInRange(x, 0.0, 100.0, isLowerBoundStrict, isUpperBoundStrict, messageNaN, messageLower, messageUpper); + + #endregion + + #region IsPositive + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive. + /// + /// A number that should be in range. + /// Whether or not infinity is allowed. + [DebuggerStepThrough] + public static void RequirePositive(double x) => + RequirePositive(x, DefaultIsInfinityAllowed, ERR_NAN, ERR_BELOW_RANGE, ERR_INFINITE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive. + /// + /// A number that should be in range. + /// Whether or not infinity is allowed. + [DebuggerStepThrough] + public static void RequirePositive(double x, bool isInfinityAllowed) => + RequirePositive(x, isInfinityAllowed, ERR_NAN, ERR_BELOW_RANGE, ERR_INFINITE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive. + /// + /// A number that should be in range. + /// Whether or not infinity is allowed. + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequirePositive(double x, string messageNaN, string messageLower, string messageUpper) => + RequirePositive(x, DefaultIsInfinityAllowed, messageNaN, messageLower, messageUpper); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive. + /// + /// A number that should be in range. + /// Whether or not infinity is allowed. + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequirePositive(double x, bool isInfinityAllowed, string messageNaN, string messageLower, string messageUpper) => + RequireInRange(x, 0.0, double.PositiveInfinity, !isInfinityAllowed, !isInfinityAllowed, messageNaN, messageLower, messageUpper); + + #endregion + + #region IsNonNegative + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive or zero. + /// + /// A number that should be in range. + [DebuggerStepThrough] + public static void RequireNonNegative(double x) => + RequireNonNegative(x, DefaultIsInfinityAllowed, ERR_NAN, ERR_BELOW_RANGE, ERR_INFINITE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive or zero. + /// + /// A number that should be in range. + /// Whether or not infinity is allowed. + [DebuggerStepThrough] + public static void RequireNonNegative(double x, bool isInfinityAllowed) => + RequireNonNegative(x, isInfinityAllowed, ERR_NAN, ERR_BELOW_RANGE, ERR_INFINITE); + + /// + /// Throws an ArgumentOutOfRangeException if a number is not positive or zero. + /// + /// A number that should be in range. + /// Whether or not infinity is allowed. + /// Message for the exception if the number is NaN. + /// Message for the exception if the number is too low. + /// Message for the exception if the number is too high. + [DebuggerStepThrough] + public static void RequireNonNegative(double x, bool isInfinityAllowed, string messageNaN, string messageLower, string messageUpper) => + RequireInRange(x, 0.0, double.PositiveInfinity, false, !isInfinityAllowed, messageNaN, messageLower, messageUpper); + + #endregion + + #region Exists + + /// + /// Throws an DirectoryNotFoundException if directory does not exist. + /// + /// A directory that must exist + /// Context for the error + [DebuggerStepThrough] + public static void RequireDirectory(string path, string message = default) + { + if (!Directory.Exists(path)) + { + throw new DirectoryNotFoundException(message ?? $"Does not exist: {path}"); + } + } + + /// + /// Throws an FileNotFoundException if file does not exist. + /// + /// A file that must exist + /// Context for the error + [DebuggerStepThrough] + public static void RequireFile(string path, string message = default) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException(message ?? $"Does not exist: {path}"); + } + } + + #endregion + + #region Type + + [DebuggerStepThrough] + public static T RequireInstanceOf(object implementation) => + RequireInstanceOf(implementation, $"Expecting implementation of type {typeof(T).Name}. Received instance of {implementation?.GetType().Name}"); + + [DebuggerStepThrough] + public static T RequireInstanceOf(object implementation, [Localizable(false)] string message) + { + if (implementation is T t) return t; + + throw new ArgumentException(message, nameof(implementation)); + } + + #endregion + + #region Collections + + [DebuggerStepThrough] + public static void RequireUniqueElements(IEnumerable enumerable, Func keySelector) => + RequireUniqueElements(enumerable, keySelector, $"Expecting unique instances of {typeof(T).Name}"); + + [DebuggerStepThrough] + public static void RequireUniqueElements(IEnumerable enumerable, Func keySelector, [Localizable(false)] string message) + { + if (enumerable.GroupBy(keySelector).All(g => g.Count() == 1)) return; + + throw new ArgumentException(message, nameof(enumerable)); + } + + #endregion + + #region Defaults + + private const bool DefaultLowerBoundStrictness = true; + private const bool DefaultUpperBoundStrictness = true; + private const bool DefaultIsInfinityAllowed = false; + + #endregion + } +} diff --git a/RVis.Base/Constant.cs b/RVis.Base/Constant.cs new file mode 100644 index 0000000..34732bc --- /dev/null +++ b/RVis.Base/Constant.cs @@ -0,0 +1,30 @@ +using System; + +namespace RVis.Base +{ + public static class Constant + { + #region Maths + + /// + /// Number of digits in the mantissa of double precision floating point numbers. + /// + /// Probably system dependent, but in this case taken from %lt;Visual Studio root>\VC\include\float.h + /// + public const int DBL_MANT_DIG = 53; + + /// + /// The smallest number, x, s.t. 1.0 + x != 1. + /// + /// Approx. 2.220446049250313e-16. + public static readonly double EPSILON = Math.Pow(2.0, 1.0 - DBL_MANT_DIG); + + /// + /// Used for checking if two floating point numbers are pretty much the same. + /// + /// Approx. 1.490116119384765625e-08. + public static readonly double TOLERANCE = Math.Sqrt(EPSILON); + + #endregion + } +} diff --git a/RVis.Base/Diagnostics/Logging.cs b/RVis.Base/Diagnostics/Logging.cs new file mode 100644 index 0000000..686f860 --- /dev/null +++ b/RVis.Base/Diagnostics/Logging.cs @@ -0,0 +1,63 @@ +using NLog; +using NLog.Config; +using NLog.Targets; +using RVis.Base.Extensions; +using System; +using System.Configuration; +using System.IO; +using static RVis.Base.Check; +using static System.Globalization.CultureInfo; + +namespace RVis.Base +{ + public static class Logging + { + public static void Configure(params string[] names) + { + Configure(names, null); + } + + public static void Configure(string name, string logLevel) + { + Configure(new[] { name }, LogLevel.FromString(logLevel)); + } + + public static void Configure(string[] names, LogLevel logLevel = default) + { + RequireNotNull(names); + + var directory = Path.Combine(DirectoryOps.ApplicationDataDirectory.FullName, "Log"); + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); + + var loggingConfiguration = new LoggingConfiguration(); + var layout = @"${date:format=HH\:mm\:ss} ${message} ${exception:format=toString}"; + var fileNameBase = DateTime.UtcNow.ToString("o", InvariantCulture).ToValidFileName(); + + foreach (var name in names) + { + var fileTarget = new FileTarget(); + loggingConfiguration.AddTarget("file." + name, fileTarget); + + var fileName = $"{name}.{fileNameBase}.log"; + fileTarget.FileName = Path.Combine(directory, fileName); + + fileTarget.Layout = layout; + + var minLevel = logLevel; + if (minLevel == default) + { + var minLevelAsString = ConfigurationManager.AppSettings[$"{name}.Log.Level"]; + if (minLevelAsString.IsAString()) minLevel = LogLevel.FromString(minLevelAsString); + } + minLevel = minLevel ?? LogLevel.Off; + + var rule = new LoggingRule($"{name}.*", minLevel, fileTarget); + loggingConfiguration.LoggingRules.Add(rule); + } + + LogManager.Configuration = loggingConfiguration; + } + + public static ILogger Create(string name) => LogManager.GetLogger(name); + } +} diff --git a/RVis.Base/Enum.cs b/RVis.Base/Enum.cs new file mode 100644 index 0000000..6106e9d --- /dev/null +++ b/RVis.Base/Enum.cs @@ -0,0 +1,12 @@ +using System; + +namespace RVis.Base +{ + [Flags] + public enum ObservableQualifier + { + Add = 0b0000_0000_0000_0001, + Remove = 0b0000_0000_0000_0010, + Change = 0b0000_0000_0000_0100 + } +} diff --git a/RVis.Base/Extensions/ArrayExt.cs b/RVis.Base/Extensions/ArrayExt.cs new file mode 100644 index 0000000..162f06d --- /dev/null +++ b/RVis.Base/Extensions/ArrayExt.cs @@ -0,0 +1,11 @@ +namespace RVis.Base.Extensions +{ + public static class ArrayExt + { + public static bool IsNullOrEmpty(this T[] t) => + t == default || t.Length == 0; + + public static bool IsEmpty(this T[] t) => + t.Length == 0; + } +} diff --git a/RVis.Base/Extensions/BoolExt.cs b/RVis.Base/Extensions/BoolExt.cs new file mode 100644 index 0000000..a747c04 --- /dev/null +++ b/RVis.Base/Extensions/BoolExt.cs @@ -0,0 +1,8 @@ +namespace RVis.Base.Extensions +{ + public static class BoolExt + { + public static bool IsTrue(this bool? b) => + b == true; + } +} diff --git a/RVis.Base/Extensions/CollExt.cs b/RVis.Base/Extensions/CollExt.cs new file mode 100644 index 0000000..5d09d91 --- /dev/null +++ b/RVis.Base/Extensions/CollExt.cs @@ -0,0 +1,96 @@ +using LanguageExt; +using System; +using System.Collections.Generic; +using System.Linq; +using static RVis.Base.Check; +using static RVis.Base.Extensions.NumExt; + +namespace RVis.Base.Extensions +{ + public static class CollExt + { + public static int FindIndex(this Arr source, Predicate match) => + FindIndex(source, 0, match); + + public static int FindIndex(this IReadOnlyList source, Predicate match) => + FindIndex(source, 0, match); + + public static int FindIndex(this IReadOnlyList source, int startIndex, Predicate match) + { + RequireTrue(startIndex >= 0); + + for (var i = startIndex; i < source.Count; ++i) + { + if (match(source[i])) return i; + } + + return NOT_FOUND; + } + + public static bool IsCollection(this ICollection c) => c?.Any() == true; + + public static bool IsEmpty(this ICollection c) => !c.Any(); + + public static void RemoveIf(this IList l, Func predicate) + { + var toRemove = l.Where(predicate).ToArray(); + foreach (var t in toRemove) l.Remove(t); + } + + public static int MaxIndex(this IEnumerable sequence) where T : IComparable + { + var maxIndex = NOT_FOUND; + var maxValue = default(T); + var index = 0; + + foreach (var value in sequence) + { + if (value.CompareTo(maxValue) > 0 || maxIndex.IsntFound()) + { + maxIndex = index; + maxValue = value; + } + ++index; + } + + return maxIndex; + } + + public static int MinIndex(this IEnumerable sequence) where T : IComparable + { + var minIndex = NOT_FOUND; + var minValue = default(T); + var index = 0; + + foreach (var value in sequence) + { + if (value.CompareTo(minValue) < 0 || minIndex.IsntFound()) + { + minIndex = index; + minValue = value; + } + ++index; + } + + return minIndex; + } + + public static bool AllSame(this IEnumerable sequence, Func likeness) => + sequence.GroupBy(likeness).Count() == 1; + + public static bool NotAllSame(this IEnumerable sequence, Func likeness) => + !AllSame(sequence, likeness); + + public static bool AllUnique(this IEnumerable sequence, Func discriminator) => + sequence.GroupBy(discriminator).All(g => g.Count() == 1); + + public static bool NotAllUnique(this IEnumerable sequence, Func discriminator) => + !AllUnique(sequence, discriminator); + + public static void Add(this IDictionary dictionary, (T, U) item) => + dictionary.Add(item.Item1, item.Item2); + + public static void AddRange(this IDictionary dictionary, IEnumerable<(T, U)> items) => + items.Iter(dictionary.Add); + } +} diff --git a/RVis.Base/Extensions/EnumExt.cs b/RVis.Base/Extensions/EnumExt.cs new file mode 100644 index 0000000..8427f15 --- /dev/null +++ b/RVis.Base/Extensions/EnumExt.cs @@ -0,0 +1,63 @@ +using LanguageExt; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using static RVis.Base.Check; +using static System.Enum; + +namespace RVis.Base.Extensions +{ + public static class EnumExt + { + public static Arr GetFlags() where T : Enum + { + var flags = new List(); + var flag = 0b0001; + var value = (object)flag; + + while (IsDefined(typeof(T), value)) + { + flags.Add((T)value); + flag <<= 1; + value = flag; + } + + return flags.ToArr(); + } + + public static bool IsAdd(this ObservableQualifier observableQualifier) => + (observableQualifier & ObservableQualifier.Add) != 0; + + public static bool IsChange(this ObservableQualifier observableQualifier) => + (observableQualifier & ObservableQualifier.Change) != 0; + + public static bool IsRemove(this ObservableQualifier observableQualifier) => + (observableQualifier & ObservableQualifier.Remove) != 0; + + public static bool IsAddOrChange(this ObservableQualifier observableQualifier) => + (observableQualifier & (ObservableQualifier.Add | ObservableQualifier.Change)) != 0; + + public static string GetDescription(this Enum value) + { + RequireNotNull(value); + + var type = value.GetType(); + var name = GetName(type, value); + + if (name.IsAString()) + { + var field = type.GetField(name); + if (field != default) + { + var customAttribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); + if (customAttribute is DescriptionAttribute descriptionAttribute) + { + return descriptionAttribute.Description; + } + } + } + + return default; + } + } +} diff --git a/RVis.Base/Extensions/FxExt.cs b/RVis.Base/Extensions/FxExt.cs new file mode 100644 index 0000000..98f6b55 --- /dev/null +++ b/RVis.Base/Extensions/FxExt.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.IO; +using System.Reflection; +using static RVis.Base.Check; + +namespace RVis.Base.Extensions +{ + public static class FxExt + { + public static bool HasNoSchema(this DataTable dataTable) => + dataTable.Columns.Count == 0; + + public static bool IsEmpty(this DataTable dataTable) => + dataTable.Rows.Count == 0; + + public static string GetDirectory(this Assembly assembly) + { + RequireNotNull(assembly); + + var uri = new UriBuilder(assembly.CodeBase); + var path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } + + public static string AsMmbrString(this Version version) => + $"{version.Major}.{version.Minor}.{version.Build:00000}.{version.Revision}"; + + public static bool IsGreaterThan(this T lhs, T rhs) => + Comparer.Default.Compare(lhs, rhs) > 0; + + public static bool IsGreaterThanOrEqualTo(this T lhs, T rhs) => + Comparer.Default.Compare(lhs, rhs) >= 0; + + public static bool IsLessThan(this T lhs, T rhs) => + Comparer.Default.Compare(lhs, rhs) < 0; + + public static bool IsLessThanOrEqualTo(this T lhs, T rhs) => + Comparer.Default.Compare(lhs, rhs) <= 0; + + public static void InsertInOrdered(this IList list, T t, Func keyLookup) + { + RequireNotNull(list); + RequireNotNull(keyLookup); + + if (list.IsEmpty()) + { + list.Add(t); + } + else + { + var index = 0; + + var sortKey = keyLookup(t); + + while (index < list.Count && keyLookup(list[index]).IsLessThan(sortKey)) + { + ++index; + } + + list.Insert(index, t); + } + } + } +} diff --git a/RVis.Base/Extensions/LangExt.cs b/RVis.Base/Extensions/LangExt.cs new file mode 100644 index 0000000..f4426f1 --- /dev/null +++ b/RVis.Base/Extensions/LangExt.cs @@ -0,0 +1,31 @@ +using LanguageExt; +using System.Collections.Generic; +using static LanguageExt.Prelude; + +namespace RVis.Base.Extensions +{ + public static class LangExt + { + public static T AssertSome(this Option optionT, string failMessage) => + optionT.IfNone(() => throw new ValueIsNoneException(failMessage)); + + public static T AssertSome(this Option optionT) => + optionT.IfNone(() => throw ValueIsNoneException.Default); + + public static R AssertRight(this Either eitherT, string failMessage) => + eitherT.IfLeft(() => throw new EitherIsNotRightException(failMessage)); + + public static R AssertRight(this Either eitherT) => + eitherT.IfLeft(() => throw new EitherIsNotRightException()); + + public static Option LookUp(this Arr<(T, U)> arr, T t) => arr + .Find(p => EqualityComparer.Default.Equals(p.Item1, t)) + .Match(q => Some(q.Item2), () => None); + + public static bool ContainsNone(this IEnumerable> options) => + options.Exists(o => o.IsNone); + + public static Option NoneOf() => + None; + } +} diff --git a/RVis.Base/Extensions/NumExt.cs b/RVis.Base/Extensions/NumExt.cs new file mode 100644 index 0000000..baeb630 --- /dev/null +++ b/RVis.Base/Extensions/NumExt.cs @@ -0,0 +1,85 @@ +using static RVis.Base.Constant; +using static System.Double; +using static System.Math; + +namespace RVis.Base.Extensions +{ + public static class NumExt + { + public const int NOT_FOUND = -1; + private const double POSITIVE = 1.0; + private const double ZERO = 0.0; + private const double NEGATIVE = -1.0; + + public static double? ToNullable(this double d) => + IsNaN(d) ? default(double?) : d; + + public static double FromNullable(this double? d) => + d ?? NaN; + + public static double GetSignum(this double d) => + d == 0.0 ? ZERO : d < 0.0 ? NEGATIVE : POSITIVE; + + public static bool IsFound(this int i) + => i != NOT_FOUND; + + public static bool IsntFound(this int i) + => !i.IsFound(); + + public static double ToSigFigs(this double d, int digits) + { + if (d == 0.0) return d; + var scale = Pow(10, Floor(Log10(Abs(d))) + 1); + return scale * Round(d / scale, digits); + } + + public static bool IsEqualTo(this double lhs, double rhs) => + Abs(lhs - rhs) < TOLERANCE; + + public static bool IsInClosedInterval(this double d, double leftBound, double rightBound) => + d >= leftBound && d <= rightBound; + + public static double GetPreviousOrderOfMagnitude(this double d) + { + var signum = d.GetSignum(); + + if (signum == ZERO) return -1.0; + + var oom = GetOrderOfMagnitude(d, signum == POSITIVE); + + return Pow(10.0, oom) * signum; + } + + public static double GetNextOrderOfMagnitude(this double d) + { + var signum = d.GetSignum(); + + if (signum == ZERO) return 1.0; + + var oom = GetOrderOfMagnitude(d, signum == NEGATIVE); + + return Pow(10.0, oom) * signum; + } + + private static double GetOrderOfMagnitude(double from, bool goDownwards) + { + var absd = Abs(from); + var log10d = Log10(absd); + + double oom; + + if (goDownwards) + { + oom = Floor(log10d); + if (oom == log10d) --oom; + } + else + { + oom = Ceiling(log10d); + if (oom == log10d) ++oom; + } + + return oom; + } + } +} diff --git a/RVis.Base/Extensions/ObjExt.cs b/RVis.Base/Extensions/ObjExt.cs new file mode 100644 index 0000000..19fd645 --- /dev/null +++ b/RVis.Base/Extensions/ObjExt.cs @@ -0,0 +1,17 @@ +namespace RVis.Base.Extensions +{ + public static class ObjExt + { + public static bool Resolve(this object o, out T t) + { + if (!(o is T u)) + { + t = default; + return false; + } + + t = u; + return true; + } + } +} diff --git a/RVis.Base/Extensions/StrExt.cs b/RVis.Base/Extensions/StrExt.cs new file mode 100644 index 0000000..adbe1f9 --- /dev/null +++ b/RVis.Base/Extensions/StrExt.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using static System.Char; +using static System.Convert; +using static System.Environment; +using static System.Globalization.CultureInfo; +using static System.String; + +namespace RVis.Base.Extensions +{ + public static class StrExt + { + public static string CheckParseValue(this string s) + { + if (s.IsAString()) + { + T _ = default; + var typeT = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + + try + { + _ = (T)ChangeType(s, typeT, InvariantCulture); + } + catch (Exception) + { + throw new ArgumentException($"Valid {typeT.Name.ToLowerInvariant()} required"); + } + } + + return s; + } + + public static bool IsGreaterThan(this string lhs, string rhs) => + Compare(lhs, rhs, StringComparison.InvariantCulture) > 0; + + public static bool IsGreaterThanOrEqualTo(this string lhs, string rhs) => + Compare(lhs, rhs, StringComparison.InvariantCulture) >= 0; + + public static bool IsLessThan(this string lhs, string rhs) => + Compare(lhs, rhs, StringComparison.InvariantCulture) < 0; + + public static bool IsLessThanOrEqualTo(this string lhs, string rhs) => + Compare(lhs, rhs, StringComparison.InvariantCulture) <= 0; + + public static string RejectEmpty(this string s) => + IsNullOrWhiteSpace(s) ? null : s; + + public static string ToValidFileName(this string s, string replaceWith = "_") => + _invalid.Replace(s, replaceWith); + + public static string PascalToHyphenated(this string s) => + Regex.Replace(s, @"(\S)([A-Z])", @"$1-$2"); + + public static string ToKey(this string s) => + new string(s.Where(c => IsLetterOrDigit(c)).ToArray()).ToLowerInvariant(); + + public static string Elide(this string s, int maxLen, string suffix = "...") => + s.Length <= maxLen ? + s : + s.Substring(0, maxLen - (suffix ?? Empty).Length) + suffix; + + public static string ExpandPath(this string s) => + s?.StartsWith(_docsPrefix, StringComparison.InvariantCulture) == true + ? Path.Combine( + GetFolderPath(SpecialFolder.MyDocuments), + s.Substring(_docsPrefix.Length) + ) + : s; + + public static string ContractPath(this string s) + { + var myDocuments = GetFolderPath(SpecialFolder.MyDocuments); + if (s?.StartsWith(myDocuments, StringComparison.InvariantCultureIgnoreCase) == true) + { + s = s.Substring(myDocuments.Length); + s = s.TrimStart(Path.DirectorySeparatorChar); + s = _docsPrefix + s; + } + return s; + } + + public static string ToMD5Hash(this string s) + { + if (_md5HashMemo.TryGetValue(s, out string hash)) return hash; + + var bytes = Encoding.UTF8.GetBytes(s); + hash = BitConverter.ToString(_md5.ComputeHash(bytes)).Replace("-", Empty); + _md5HashMemo.TryAdd(s, hash); + + return hash; + } + + public static string Replace(this string s, char[] toReplace, string newValue) => + Join(newValue, s.Split(toReplace, StringSplitOptions.RemoveEmptyEntries)); + + public static bool EqualsCI(this string s, string t) => + string.Equals(s, t, StringComparison.InvariantCultureIgnoreCase); + + public static bool DoesNotEqualCI(this string s, string t) => + !EqualsCI(s, t); + + public static bool IsAString(this string s) => + !IsNullOrWhiteSpace(s); + + public static bool IsntAString(this string s) => + !IsAString(s); + + public static string[] Tokenize(this string s, char[] separators = null) => + s.Split(separators ?? _separators, StringSplitOptions.RemoveEmptyEntries); + + public static bool ContainsWhiteSpace(this string s) => + s == default ? false : s.Any(IsWhiteSpace); + + public static string ToCsvQuoted(this string s) => + s.ContainsWhiteSpace() ? "\"" + s + "\"" : s; + + private static Regex GetInvalid() + { + var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars())); + var invalidRegStr = Format(InvariantCulture, @"([{0}]*\.+$)|([{0}]+)", invalidChars); + return new Regex(invalidRegStr); + } + + private static readonly char[] _separators = new[] { ';', ',' }; + private const string _docsPrefix = "~/"; + private static readonly Regex _invalid = GetInvalid(); + private static readonly MD5 _md5 = new MD5CryptoServiceProvider(); + private static readonly ConcurrentDictionary _md5HashMemo = new ConcurrentDictionary(); + } +} diff --git a/RVis.Base/Extensions/TupleExt.cs b/RVis.Base/Extensions/TupleExt.cs new file mode 100644 index 0000000..25b18b7 --- /dev/null +++ b/RVis.Base/Extensions/TupleExt.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RVis.Base.Extensions +{ + public static class TupleExt + { + public static U Snd(this IEnumerable<(T, U)> list, T t) => + list.Single(p => EqualityComparer.Default.Equals(p.Item1, t)).Item2; + + public static bool IsEmpty(this ValueTuple tuple) => + EqualityComparer.Default.Equals(tuple.Item1, default) && + EqualityComparer.Default.Equals(tuple.Item2, default); + + public static bool IsntEmpty(this ValueTuple tuple) => + !IsEmpty(tuple); + + public static bool IsEmpty(this ValueTuple tuple) => + EqualityComparer.Default.Equals(tuple.Item1, default) && + EqualityComparer.Default.Equals(tuple.Item2, default) && + EqualityComparer.Default.Equals(tuple.Item3, default); + + public static bool IsntEmpty(this ValueTuple tuple) => + !IsEmpty(tuple); + + public static bool IsEmpty(this ValueTuple tuple) => + EqualityComparer.Default.Equals(tuple.Item1, default) && + EqualityComparer.Default.Equals(tuple.Item2, default) && + EqualityComparer.Default.Equals(tuple.Item3, default) && + EqualityComparer.Default.Equals(tuple.Item4, default); + + public static bool IsntEmpty(this ValueTuple tuple) => + !IsEmpty(tuple); + } +} diff --git a/RVis.Base/IO/DirectoryOps.cs b/RVis.Base/IO/DirectoryOps.cs new file mode 100644 index 0000000..5f673a6 --- /dev/null +++ b/RVis.Base/IO/DirectoryOps.cs @@ -0,0 +1,76 @@ +using RVis.Base.Extensions; +using System.IO; +using static RVis.Base.Meta; +using static System.Environment; +using static System.IO.Directory; +using static System.IO.Path; + +namespace RVis.Base +{ + public static class DirectoryOps + { + public static DirectoryInfo ApplicationDataDirectory => GetApplicationDataDirectory(null); + + public static DirectoryInfo DocumentsDirectory => GetDocumentsDirectory(null); + + private static DirectoryInfo GetApplicationDataDirectory(string subDirectory) + { + if (_applicationDataDirectory.IsntAString()) EnsureApplicationDataDirectory(); + + if (subDirectory.IsntAString()) return new DirectoryInfo(_applicationDataDirectory); + + var path = Combine(_applicationDataDirectory, subDirectory); + + if (!Exists(path)) return CreateDirectory(path); + + return new DirectoryInfo(path); + } + + private static DirectoryInfo GetDocumentsDirectory(string subDirectory) + { + if (_documentsDirectory.IsntAString()) + { + EnsureDocumentsDirectory(); + } + + if (subDirectory.IsntAString()) + { + return new DirectoryInfo(_documentsDirectory); + } + + var path = Combine(_documentsDirectory, subDirectory); + if (!Exists(path)) + { + return CreateDirectory(path); + } + + return new DirectoryInfo(path); + } + + private static void EnsureApplicationDataDirectory() + { + var path = GetFolderPath(SpecialFolder.LocalApplicationData); + + path = Combine(path, Company); + + if (!Exists(path)) CreateDirectory(path); + + path = Combine(path, Product); + + if (!Exists(path)) CreateDirectory(path); + + _applicationDataDirectory = path; + } + + private static void EnsureDocumentsDirectory() + { + var path = GetFolderPath(SpecialFolder.MyDocuments); + path = Combine(path, Product); + if (!Exists(path)) CreateDirectory(path); + _documentsDirectory = path; + } + + private static string _applicationDataDirectory; + private static string _documentsDirectory; + } +} diff --git a/RVis.Base/Interface.cs b/RVis.Base/Interface.cs new file mode 100644 index 0000000..91e4e1f --- /dev/null +++ b/RVis.Base/Interface.cs @@ -0,0 +1,12 @@ +namespace RVis.Base +{ + public interface IDeepCloneable + { + object DeepClone(); + } + + public interface IDeepCloneable : IDeepCloneable + { + new T DeepClone(); + } +} diff --git a/RVis.Base/Meta.cs b/RVis.Base/Meta.cs new file mode 100644 index 0000000..a1018d0 --- /dev/null +++ b/RVis.Base/Meta.cs @@ -0,0 +1,53 @@ +using RVis.Base.Extensions; +using System; +using System.IO; +using System.Reflection; + +namespace RVis.Base +{ + public static class Meta + { + public static DateTime FileDate + { + get + { + var assembly = Assembly.GetExecutingAssembly(); + return File.GetLastWriteTime(assembly.Location); + } + } + + public static string Version + { + get + { + var assembly = Assembly.GetExecutingAssembly(); + var version = assembly.GetName().Version; + return version.AsMmbrString(); + } + } + + public static string VersionMajorDotMinor + { + get + { + var assembly = Assembly.GetExecutingAssembly(); + var version = assembly.GetName().Version; + return $"{version.Major}.{version.Minor}"; + } + } + + public static string Product => + ((AssemblyProductAttribute)Attribute.GetCustomAttribute( + Assembly.GetExecutingAssembly(), + typeof(AssemblyProductAttribute) + ) + ).Product; + + public static string Company => + ((AssemblyCompanyAttribute)Attribute.GetCustomAttribute( + Assembly.GetExecutingAssembly(), + typeof(AssemblyCompanyAttribute) + ) + ).Company; + } +} diff --git a/RVis.Base/Properties/Resources.Designer.cs b/RVis.Base/Properties/Resources.Designer.cs new file mode 100644 index 0000000..c100990 --- /dev/null +++ b/RVis.Base/Properties/Resources.Designer.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RVis.Base.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RVis.Base.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The number is above the specified range. + /// + internal static string ERR_ABOVE_RANGE { + get { + return ResourceManager.GetString("ERR_ABOVE_RANGE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number is below the specified range. + /// + internal static string ERR_BELOW_RANGE { + get { + return ResourceManager.GetString("ERR_BELOW_RANGE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The objects are equal. + /// + internal static string ERR_EQUAL { + get { + return ResourceManager.GetString("ERR_EQUAL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number is infinite. + /// + internal static string ERR_INFINITE { + get { + return ResourceManager.GetString("ERR_INFINITE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number is not a number (NaN). + /// + internal static string ERR_NAN { + get { + return ResourceManager.GetString("ERR_NAN", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The numbers are not equal, within the specified tolerance. + /// + internal static string ERR_NOT_ALMOST_EQUAL { + get { + return ResourceManager.GetString("ERR_NOT_ALMOST_EQUAL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The objects are not equal. + /// + internal static string ERR_NOT_EQUAL { + get { + return ResourceManager.GetString("ERR_NOT_EQUAL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The condition is not false. + /// + internal static string ERR_NOT_FALSE { + get { + return ResourceManager.GetString("ERR_NOT_FALSE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object is not null. + /// + internal static string ERR_NOT_NULL { + get { + return ResourceManager.GetString("ERR_NOT_NULL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The condition is not true. + /// + internal static string ERR_NOT_TRUE { + get { + return ResourceManager.GetString("ERR_NOT_TRUE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object is null. + /// + internal static string ERR_NULL { + get { + return ResourceManager.GetString("ERR_NULL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The string is not null or empty. + /// + internal static string ERR_STRING_NOT_NULL_OR_EMPTY { + get { + return ResourceManager.GetString("ERR_STRING_NOT_NULL_OR_EMPTY", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The string is null or empty. + /// + internal static string ERR_STRING_NULL_OR_EMPTY { + get { + return ResourceManager.GetString("ERR_STRING_NULL_OR_EMPTY", resourceCulture); + } + } + } +} diff --git a/RVis.Base/Properties/Resources.resx b/RVis.Base/Properties/Resources.resx new file mode 100644 index 0000000..dece05d --- /dev/null +++ b/RVis.Base/Properties/Resources.resx @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The number is above the specified range + Check + + + The number is below the specified range + Check + + + The objects are equal + Check + + + The number is infinite + Check + + + The number is not a number (NaN) + Check + + + The numbers are not equal, within the specified tolerance + Check + + + The objects are not equal + Check + + + The condition is not false + Check + + + The object is not null + Check + + + The condition is not true + Check + + + The object is null + Check + + + The string is not null or empty + Check + + + The string is null or empty + Check + + \ No newline at end of file diff --git a/RVis.Base/RVis.Base.csproj b/RVis.Base/RVis.Base.csproj new file mode 100644 index 0000000..a61f0c6 --- /dev/null +++ b/RVis.Base/RVis.Base.csproj @@ -0,0 +1,66 @@ + + + + netstandard2.0;net471 + en + AnyCPU;x64 + + + + latest + TRACE;DEBUG + + + + latest + TRACE;DEBUG + + + + latest + + + + latest + + + + latest + TRACE;DEBUG + + + + latest + TRACE;DEBUG + + + + latest + + + + latest + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/RVis.Base/UniStd/CliOpt.cs b/RVis.Base/UniStd/CliOpt.cs new file mode 100644 index 0000000..9616a72 --- /dev/null +++ b/RVis.Base/UniStd/CliOpt.cs @@ -0,0 +1,191 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using static LanguageExt.Prelude; +using static LanguageExt.Seq; +using static RVis.Base.Check; +using static RVis.Base.Extensions.StrExt; +using static System.String; +using static System.Globalization.CultureInfo; + +namespace RVis.Base +{ + public enum OptArgType + { + ShortOpt, + LongOpt, + ArgOnly + } + + public enum OptArgUsage + { + None, + Optional, + Required + } + + public sealed class CliOptSpec : Record + { + public CliOptSpec(string option, OptArgType optArgType, OptArgUsage optArgUsage) + { + RequireTrue(optArgType == OptArgType.ArgOnly || option.IsAString()); + + Option = option?.PascalToHyphenated().ToLowerInvariant(); + OptArgType = optArgType; + OptArgUsage = optArgUsage; + } + + public static readonly CliOptSpec ArgOnly = new CliOptSpec(null, OptArgType.ArgOnly, OptArgUsage.Required); + + public readonly string Option; + public readonly OptArgType OptArgType; + public readonly OptArgUsage OptArgUsage; + } + + public sealed class CliOpt : Record + { + public CliOpt(CliOptSpec cliOptSpec, string argument) + { + RequireTrue( + cliOptSpec.OptArgUsage != OptArgUsage.Required || argument.IsAString(), + $"Option {cliOptSpec.Option} requires an argument" + ); + + CliOptSpec = cliOptSpec; + Argument = argument; + } + + public CliOpt(CliOptSpec cliOptSpec) : this(cliOptSpec, null) { } + + public readonly CliOptSpec CliOptSpec; + public readonly Option Argument; + } + + public static class CliOptExt + { + public static Seq ToCliOptSpecs(this Seq optSpecs) + { + CliOptSpec StringToCliOptSpec(string optSpec) + { + var optArgType = 1 < optSpec.Length ? OptArgType.LongOpt : OptArgType.ShortOpt; + + var optArgUsage = optSpec.EndsWith("=?", StringComparison.InvariantCulture) ? + OptArgUsage.Optional : optSpec.EndsWith("=", StringComparison.InvariantCulture) ? + OptArgUsage.Required : OptArgUsage.None; + + var option = optSpec.TrimEnd('=', '?'); + + return new CliOptSpec(option, optArgType, optArgUsage); + } + + return map(optSpecs, StringToCliOptSpec); + } + + public static Seq ToCliOpts(this string[] args, Seq cliOptSpecs) => + ToCliOpts(args.ToSeq(), cliOptSpecs); + + public static Seq ToCliOpts(this Seq args, Seq cliOptSpecs) + { + CliOpt ArgToCliOpt(string arg) + { + var optArgType = arg.StartsWith("--", StringComparison.InvariantCulture) ? + OptArgType.LongOpt : arg.StartsWith("-", StringComparison.InvariantCulture) ? + OptArgType.ShortOpt : OptArgType.ArgOnly; + + if (optArgType == OptArgType.ArgOnly) return new CliOpt(CliOptSpec.ArgOnly, arg); + + arg = arg.TrimStart('-'); + + string option; + string argument; + + if (optArgType == OptArgType.LongOpt) + { + var index = arg.IndexOf('='); + if (index.IsntFound()) + { + option = arg; + argument = null; + } + else + { + option = arg.Substring(0, index); + argument = arg.Substring(index + 1); + } + } + else + { + option = arg.Substring(0, 1); + argument = arg.Length > 1 ? arg.Substring(1) : null; + } + + if (argument.IsntAString()) argument = null; + + var cliOptSpec = cliOptSpecs.Find(cos => cos.Option == option); + + var cliOpt = cliOptSpec.Match( + Some: cos => new CliOpt(cos, argument), + None: () => throw new ArgumentOutOfRangeException(nameof(arg), option, $"Unknown option: {option}") + ); + + return cliOpt; + } + + return map(args, ArgToCliOpt); + } + + public static Option GetOpt(this Seq cliOpts, params T[] ts) + { + var options = ts.Map(t => Convert.ToString(t,InvariantCulture).PascalToHyphenated().ToLowerInvariant()).ToSeq(); + return GetOptImpl(cliOpts, options); + } + + public static Option GetOpt(this Seq cliOpts, params string[] options) => + GetOptImpl(cliOpts, options.Map(s => s.PascalToHyphenated().ToLowerInvariant()).ToSeq()); + + public static Either> GetOpt(this Seq cliOpts, params T[] ts) => + GetOptImpl(cliOpts, ts.Map(t => Convert.ToString(t, InvariantCulture).PascalToHyphenated().ToLowerInvariant()).ToSeq()); + + public static Either> GetOpt(this Seq cliOpts, params string[] options) => + GetOptImpl(cliOpts, options.ToSeq()); + + private static Option GetOptImpl(this Seq cliOpts, Seq options) => + cliOpts.Find(co => options.Exists(o => co.CliOptSpec.Option == o)); + + public static Either> GetOptImpl(this Seq cliOpts, Seq options) + { + var cliOptOrNotFound = GetOptImpl(cliOpts, options).ToEither($"Option not found: {Join(", ", options)}"); + + Either> AttemptConvertArgument(CliOpt co) + { + if (co.CliOptSpec.OptArgUsage == OptArgUsage.Optional && co.Argument.IsNone) + { + return Right>(None); + } + + var t = from text in co.Argument + from arg in convert(text) + select arg; + + if (t.IsNone) + { + return $"Option {Join(", ", options)} requires a valid {typeof(T).Name}"; + } + + return t; + } + + return cliOptOrNotFound.Bind(AttemptConvertArgument); + } + + public static Seq GetArgs(this Seq cliOpts) => + Seq( + somes( + map( + filter(cliOpts, co => co.CliOptSpec.OptArgType == OptArgType.ArgOnly), + co => co.Argument + ) + ) + ); + } +} diff --git a/RVis.Data/Extensions/TableExt.cs b/RVis.Data/Extensions/TableExt.cs new file mode 100644 index 0000000..de5e783 --- /dev/null +++ b/RVis.Data/Extensions/TableExt.cs @@ -0,0 +1,48 @@ +using LanguageExt; +using System; +using System.Collections.Generic; +using System.Linq; +using static RVis.Base.Check; + +namespace RVis.Data.Extensions +{ + public static class TableExt + { + public static IDataColumn ToDataColumn(this Array data, string name) + { + var elementType = data.GetType().GetElementType(); + var typedColumn = typeof(DataColumn<>).MakeGenericType(elementType); + var instance = Activator.CreateInstance(typedColumn, new object[] { name, data }); + return (IDataColumn)instance; + } + + public static IDataColumn ToDataColumn(this T[] data, string name) => new DataColumn(name, data); + + public static IDataColumn ToDataColumn(this IEnumerable data, string name) => new DataColumn(name, data); + + public static NumDataColumn ToDataColumn(this double[] data, string name) => new NumDataColumn(name, data); + + public static NumDataColumn ToDataColumn(this IEnumerable data, string name) => new NumDataColumn(name, data); + + public static IDataColumn Get(this IDataTable dataTable, string name) => EnsureDataType(dataTable[name]); + + public static IDataColumn Get(this IDataTable dataTable, int index) => EnsureDataType(dataTable[index]); + + public static NumDataColumn GetIndependentVariable(this NumDataTable dataTable) + { + RequireTrue(dataTable.NColumns > 0, $"{nameof(GetIndependentVariable)} called with empty DataTable"); + if (!(dataTable[0] is NumDataColumn dataColumn)) throw new ArgumentException($"{nameof(GetIndependentVariable)} called against non-numeric IV", nameof(dataTable)); + return dataColumn; + } + + public static Arr GetDependentVariables(this NumDataTable dataTable) + { + return dataTable.NumDataColumns.Skip(1).ToArr(); + } + + private static IDataColumn EnsureDataType(IDataColumn dataColumn) => + dataColumn is IDataColumn dt ? + dt : + throw new ArgumentException($"Requested column of type {typeof(T).Name}; runtime type is {dataColumn.GetDataType().Name}", nameof(dataColumn)); + } +} diff --git a/RVis.Data/Fx/FxData.cs b/RVis.Data/Fx/FxData.cs new file mode 100644 index 0000000..58e6f93 --- /dev/null +++ b/RVis.Data/Fx/FxData.cs @@ -0,0 +1,68 @@ +using CsvHelper; +using System.Data; +using System.IO; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using FxDataTable = System.Data.DataTable; + +namespace RVis.Data +{ + public static class FxData + { + public static FxDataTable LoadFromCSV(string pathToCSV) + { + RequireFile(pathToCSV); + + var dataTable = new FxDataTable(); + + using (var streamReader = new StreamReader(pathToCSV)) + using (var csvReader = new CsvReader(streamReader)) + { + csvReader.Read(); + csvReader.ReadHeader(); + + foreach (var header in csvReader.Context.HeaderRecord) + { + dataTable.Columns.Add(new DataColumn(header, typeof(T))); + } + + while (csvReader.Read()) + { + var dataRow = dataTable.NewRow(); + dataRow.ItemArray = Range(0, dataTable.Columns.Count) + .Map(i => csvReader.GetField(i)) + .Cast() + .ToArray(); + dataTable.Rows.Add(dataRow); + } + } + + return dataTable; + } + + public static void SaveToCSV(FxDataTable dataTable, string pathToCSV) + { + using (var streamWriter = new StreamWriter(pathToCSV)) + using (var csvWriter = new CsvWriter(streamWriter)) + { + foreach (DataColumn column in dataTable.Columns) + { + csvWriter.WriteField(column.ColumnName); + } + + csvWriter.NextRecord(); + + foreach (DataRow dataRow in dataTable.Rows) + { + for (var i = 0; i < dataTable.Columns.Count; ++i) + { + csvWriter.WriteField(dataRow.Field(i)); + } + + csvWriter.NextRecord(); + } + } + } + } +} diff --git a/RVis.Data/RVis.Data.csproj b/RVis.Data/RVis.Data.csproj new file mode 100644 index 0000000..f6b5122 --- /dev/null +++ b/RVis.Data/RVis.Data.csproj @@ -0,0 +1,54 @@ + + + + netstandard2.0;net471 + AnyCPU;x64 + + + + latest + TRACE;DEBUG + + + + latest + TRACE;DEBUG + + + + latest + + + + latest + + + + latest + TRACE;DEBUG + + + + latest + TRACE;DEBUG + + + + latest + + + + latest + + + + + + + + + + + + + diff --git a/RVis.Data/Table/DataColumn.cs b/RVis.Data/Table/DataColumn.cs new file mode 100644 index 0000000..b49f52c --- /dev/null +++ b/RVis.Data/Table/DataColumn.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using static RVis.Base.Check; + +namespace RVis.Data +{ + public class DataColumn : DataColumnBase + { + public DataColumn(string name, IEnumerable data) + { + RequireNotNull(data); + + _name = name; + _data = data.ToArray(); + } + + public override string Name => _name; + + public override IReadOnlyList Data => _data; + + internal string _name; + internal T[] _data; + + protected DataColumn() { } + } +} diff --git a/RVis.Data/Table/DataColumnBase.cs b/RVis.Data/Table/DataColumnBase.cs new file mode 100644 index 0000000..0046d36 --- /dev/null +++ b/RVis.Data/Table/DataColumnBase.cs @@ -0,0 +1,25 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RVis.Data +{ + [ProtoContract] + public abstract class DataColumnBase : IDataColumn + { + public abstract string Name { get; } + + public int Length => Data.Count; + + public Type GetDataType() => typeof(T); + + public T this[int row] => Data[row]; + + public abstract IReadOnlyList Data { get; } + + object IDataColumn.this[int row] => Data[row]; + + IReadOnlyList IDataColumn.Data => Data.Cast().ToArray(); + } +} diff --git a/RVis.Data/Table/DataTable.cs b/RVis.Data/Table/DataTable.cs new file mode 100644 index 0000000..920c7be --- /dev/null +++ b/RVis.Data/Table/DataTable.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RVis.Data +{ + public sealed class DataTable : DataTableBase + { + public DataTable(string name, IEnumerable columns) + { + Check(columns); + + Name = name; + DataColumns = Array.AsReadOnly(columns.ToArray()); + } + + public DataTable(string name, params IDataColumn[] columns) + : this(name, columns as IEnumerable) + { + } + + public override string Name { get; } + + public override IReadOnlyList DataColumns { get; } + + private DataTable() { } + } +} diff --git a/RVis.Data/Table/DataTableBase.cs b/RVis.Data/Table/DataTableBase.cs new file mode 100644 index 0000000..2e3a8a1 --- /dev/null +++ b/RVis.Data/Table/DataTableBase.cs @@ -0,0 +1,34 @@ +using ProtoBuf; +using RVis.Base.Extensions; +using System.Collections.Generic; +using System.Linq; +using static RVis.Base.Check; + +namespace RVis.Data +{ + [ProtoContract] + public abstract class DataTableBase : IDataTable + { + public abstract string Name { get; } + + public abstract IReadOnlyList DataColumns { get; } + + public IReadOnlyList ColumnNames => DataColumns.Select(dc => dc.Name).ToArray(); + + public int NColumns => DataColumns.Count; + + public int NRows => 0 == NColumns ? 0 : DataColumns[0].Length; + + public bool HasColumn(string name) => DataColumns.Count(c => c.Name == name) == 1; + + public IDataColumn this[string name] => DataColumns.SingleOrDefault(c => c.Name == name); + + public IDataColumn this[int index] => DataColumns[index]; + + protected static void Check(IEnumerable columns) + { + RequireNotNull(columns); + RequireTrue(!columns.Any() || columns.AllSame(dc => dc.Length)); + } + } +} diff --git a/RVis.Data/Table/Interface.cs b/RVis.Data/Table/Interface.cs new file mode 100644 index 0000000..feb58d3 --- /dev/null +++ b/RVis.Data/Table/Interface.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace RVis.Data +{ + public interface IDataTable + { + IDataColumn this[int index] { get; } + IDataColumn this[string name] { get; } + IReadOnlyList ColumnNames { get; } + IReadOnlyList DataColumns { get; } + string Name { get; } + int NColumns { get; } + int NRows { get; } + bool HasColumn(string name); + } + + public interface IDataColumn + { + string Name { get; } + int Length { get; } + object this[int row] { get; } + IReadOnlyList Data { get; } + Type GetDataType(); + } + + public interface IDataColumn : IDataColumn + { + new T this[int row] { get; } + new IReadOnlyList Data { get; } + } +} diff --git a/RVis.Data/Table/NumDataColumn.cs b/RVis.Data/Table/NumDataColumn.cs new file mode 100644 index 0000000..46e127e --- /dev/null +++ b/RVis.Data/Table/NumDataColumn.cs @@ -0,0 +1,35 @@ +using ProtoBuf; +using System.Collections.Generic; +using System.Linq; +using static RVis.Base.Check; + +namespace RVis.Data +{ + [ProtoContract] + public sealed class NumDataColumn : DataColumnBase + { + public NumDataColumn() + { + } + + public NumDataColumn(string name, IEnumerable data) + { + RequireNotNull(data); + + _name = name; + _data = data.ToArray(); + } + + [ProtoIgnore] + public override string Name => _name; + + [ProtoIgnore] + public override IReadOnlyList Data => _data; + + [ProtoMember(1)] + private readonly string _name; + + [ProtoMember(2)] + private readonly double[] _data; + } +} diff --git a/RVis.Data/Table/NumDataTable.cs b/RVis.Data/Table/NumDataTable.cs new file mode 100644 index 0000000..b9b9632 --- /dev/null +++ b/RVis.Data/Table/NumDataTable.cs @@ -0,0 +1,92 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace RVis.Data +{ + [ProtoContract] + public sealed class NumDataTable : DataTableBase + { + public static void SaveToBinaryFile(NumDataTable numDataTable, string path) + { + var fileStream = new FileStream(path, FileMode.Create); + + try + { + Serializer.Serialize(fileStream, numDataTable); + } + catch (Exception ex) + { + throw new ArgumentException( + $"Failed to save binary for {nameof(NumDataTable)} to {path}", + nameof(path), + ex + ); + } + finally + { + fileStream.Close(); + } + } + + public static NumDataTable LoadFromBinaryFile(string path) + { + var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + try + { + return Serializer.Deserialize(fileStream); + } + catch (Exception ex) + { + throw new ArgumentException( + $"Failed to load binary for {nameof(NumDataTable)} from {path}", + nameof(path), + ex + ); + } + finally + { + fileStream.Close(); + } + } + + public NumDataTable() + { + + } + + public NumDataTable(string name, IEnumerable columns) + { + Check(columns); + + _name = name; + _columns = columns.ToArray(); + } + + public NumDataTable(string name, params NumDataColumn[] columns) + : this(name, columns as IEnumerable) + { + } + + [ProtoIgnore] + public override string Name => _name; + + [ProtoIgnore] + public override IReadOnlyList DataColumns => NumDataColumns; + + [ProtoIgnore] + public IReadOnlyList NumDataColumns => _columns; + + public new NumDataColumn this[string name] => NumDataColumns.Single(c => c.Name == name); + + public new NumDataColumn this[int index] => NumDataColumns[index]; + + [ProtoMember(1)] + private readonly string _name; + + [ProtoMember(2)] + private readonly NumDataColumn[] _columns; + } +} diff --git a/RVis.Model/AssemblyInfo.cs b/RVis.Model/AssemblyInfo.cs new file mode 100644 index 0000000..6cc2035 --- /dev/null +++ b/RVis.Model/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("RVis.Model.Test")] \ No newline at end of file diff --git a/RVis.Model/Code/ElementCandidate.cs b/RVis.Model/Code/ElementCandidate.cs new file mode 100644 index 0000000..0a2246b --- /dev/null +++ b/RVis.Model/Code/ElementCandidate.cs @@ -0,0 +1,66 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using System.Text.RegularExpressions; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public class ElementCandidate + { + public ElementCandidate(string name, Arr values, Arr symbolInfos) + { + RequireNotNullEmptyWhiteSpace(name); + RequireTrue(values.Count > 1); + + Name = name; + Values = values; + + AssignDefaultUnitDescription(symbolInfos); + } + + public string Name { get; } + + public Arr Values { get; } + + public string Description { get; set; } + + public string Unit { get; set; } + + public bool IsUsed { get; set; } + + public bool IsIndependentVariable { get; set; } + + private void AssignDefaultUnitDescription(Arr symbolInfos) + { + if (Description.IsntAString() || Unit.IsntAString()) + { + var reversed = symbolInfos + .Filter(si => si.Comment.IsAString() || si.Unit.IsAString()) + .Rev(); + + var symbolInfo = reversed.Find(si => si.Symbol == Name); + + if (symbolInfo.IsNone) + { + // not found so look for symbol with single character prefix e.g. Rsymbol or dsymbol + symbolInfo = reversed.Find( + si => si.Symbol?.Length == Name.Length + 1 && si.Symbol.EndsWith(Name, StringComparison.InvariantCulture) + ); + } + + if (symbolInfo.IsNone) + { + var re = new Regex($"^\\w?{Name}\\W*=[^=]"); + symbolInfo = reversed.Find(si => re.IsMatch(si.Code)); + } + + symbolInfo.IfSome(si => + { + Description = Description ?? si.Comment; + Unit = Unit ?? si.Unit; + }); + } + } + } +} diff --git a/RVis.Model/Code/Enum.cs b/RVis.Model/Code/Enum.cs new file mode 100644 index 0000000..70a698c --- /dev/null +++ b/RVis.Model/Code/Enum.cs @@ -0,0 +1,12 @@ +namespace RVis.Model +{ + public enum SymbolType + { + NonRVisType, + DataFrame, + Function, + Matrix, + List, + Vector, + } +} diff --git a/RVis.Model/Code/Interface.cs b/RVis.Model/Code/Interface.cs new file mode 100644 index 0000000..a0cfccc --- /dev/null +++ b/RVis.Model/Code/Interface.cs @@ -0,0 +1,20 @@ +using RVis.Data; + +namespace RVis.Model +{ + public interface ISymbolInfo + { + string Code { get; } + string Comment { get; } + int Length { get; } + int Level { get; } + int LineNo { get; } + string Symbol { get; } + string[] Names { get; } + double? Scalar { get; } + SymbolType SymbolType { get; } + int SymbolicExpressionType { get; } + string Unit { get; } + NumDataTable Value { get; } + } +} diff --git a/RVis.Model/Code/ManagedImport.cs b/RVis.Model/Code/ManagedImport.cs new file mode 100644 index 0000000..d1f896b --- /dev/null +++ b/RVis.Model/Code/ManagedImport.cs @@ -0,0 +1,284 @@ +using LanguageExt; +using RVis.Base.Extensions; +using RVis.Data; +using RVis.Model.Extensions; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using static LanguageExt.Prelude; +using static System.Globalization.CultureInfo; + +namespace RVis.Model +{ + public class ManagedImport : IDisposable + { + public ManagedImport(string pathToRFile, SimLibrary simLibrary) + { + _codeFileName = Path.GetFileName(pathToRFile); + SimulationName = Path.GetFileNameWithoutExtension(_codeFileName); + _codeLines = File.ReadAllLines(pathToRFile); + _simLibrary = simLibrary; + } + + ~ManagedImport() => Dispose(false); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public string SimulationName { get; set; } + + public string SimulationDescription { get; set; } + + public async Task InspectAsync(IRVisClient rVisClient) + { + var inspection = Task.Run( + () => + { + if (_pathToContainingDirectory.IsntAString()) SetUpStaging(); + var pathToCode = Path.Combine(_pathToContainingDirectory, _codeFileName); + + rVisClient.Clear(); + return rVisClient.InspectSymbols(pathToCode).ToArr(); + } + ); + + SymbolInfos = await inspection; + + UnaryFunctions = SymbolInfos + .Filter(si => + si.Level == 0 && + si.Symbol.IsAString() && + si.SymbolType == SymbolType.Function && + si.Length == 1) + .OrderBy(si => si.Symbol) + .ToArr(); + + Scalars = SymbolInfos + .Filter(si => + si.Level == 0 && + si.Symbol.IsAString() && + si.SymbolType == SymbolType.Vector && + si.Length == 1 && + si.Scalar.HasValue) + .OrderBy(si => si.Symbol) + .ToArr(); + + ScalarSets = SymbolInfos + .Filter(si => + si.Level == 0 && + si.Symbol.IsAString() && + (si.SymbolType == SymbolType.Vector || si.SymbolType == SymbolType.List) && + si.Names?.Length > 1 && + si.Value?.DataColumns.FirstOrDefault()?.Length == 1) + .OrderBy(si => si.Symbol) + .ToArr(); + + DataSets = SymbolInfos + .Filter(si => + si.Level == 0 && + si.Value?.ColumnNames.All(cn => cn.IsAString()) == true && + si.Value?.DataColumns.FirstOrDefault()?.Length > 1) + .OrderBy(si => si.Symbol) + .ToArr(); + + ParameterCandidates = Scalars.Map(s => new ParameterCandidate(s)); + + ValueCandidates = DataSets.Map(ds => new ValueCandidate(ds, SymbolInfos)); + } + + public Arr SymbolInfos { get; private set; } + + public Arr UnaryFunctions { get; private set; } + + public Arr Scalars { get; private set; } + + public Arr ScalarSets { get; private set; } + + public Arr DataSets { get; private set; } + + public Arr ParameterCandidates { get; private set; } + + public Arr ValueCandidates { get; private set; } + + public async Task SetExecutorAsync(ISymbolInfo function, ISymbolInfo formal, IRVisClient rVisClient) + { + var executor = Task.Run( + () => + { + var pathToCode = Path.Combine(_pathToContainingDirectory, _codeFileName); + + var code = new SimCode(_codeFileName, function.Symbol, formal.Symbol); + var config = new SimConfig(default, default, default, code, default, default); + + rVisClient.Clear(); + rVisClient.RunExec(pathToCode, config); + return rVisClient.TabulateExecOutput(config); + } + ); + + ExecutorOutput = await executor; + ExecutorFunction = function; + ExecutorFormal = formal; + ExecutorParameterCandidates = ParameterCandidate.CreateForExec(ExecutorFormal, SymbolInfos); + (ExecutorIndependentVariable, ExecutorValueCandidates) = ValueCandidate.CreateForExec(ExecutorOutput, SymbolInfos); + } + + public ISymbolInfo ExecutorFunction { get; private set; } + + public ISymbolInfo ExecutorFormal { get; private set; } + + public Arr ExecutorParameterCandidates { get; private set; } + + public NumDataTable ExecutorOutput { get; private set; } + + public ValueCandidate ExecutorIndependentVariable { get; private set; } + + public Arr ExecutorValueCandidates { get; private set; } + + public string ImportExecToLibrary() + { + var code = new SimCode(_codeFileName, ExecutorFunction.Symbol, ExecutorFormal.Symbol); + + var parameters = ExecutorParameterCandidates + .Filter(pc => pc.IsUsed) + .Map(pc => new SimParameter( + pc.Name, + pc.Value.ToString(InvariantCulture), + pc.Unit, + pc.Description + )); + + var input = new SimInput(parameters, isDefault: true); + + var ivValue = new SimValue( + ExecutorIndependentVariable.Name, + Array(new SimElement( + ExecutorIndependentVariable.ElementCandidates[0].Name, + true, + ExecutorIndependentVariable.ElementCandidates[0].Unit, + ExecutorIndependentVariable.ElementCandidates[0].Description + )) + ); + + var values = ExecutorValueCandidates + .Filter(vc => vc.ElementCandidates[0].IsUsed) + .Map(vc => new SimValue( + vc.Name, + Array( + new SimElement( + vc.ElementCandidates[0].Name, + false, + vc.ElementCandidates[0].Unit, + vc.ElementCandidates[0].Description + ) + )) + ); + + + var output = new SimOutput(ivValue + values); + + var config = new SimConfig(SimulationName, SimulationDescription, DateTime.UtcNow, code, input, output); + + config.WriteToFile(_pathToContainingDirectory); + + return _simLibrary.Import(_pathToContainingDirectory); + } + + public string ImportTmplToLibrary() + { + var code = new SimCode(_codeFileName, default, default); + + var parameterCandidates = ParameterCandidates.Filter(pc => pc.IsUsed); + + var parameters = parameterCandidates + .Map(pc => new SimParameter( + pc.Name, + pc.Value.ToString(InvariantCulture), + pc.Unit, + pc.Description + )); + + var input = new SimInput(parameters, isDefault: true); + + var values = ValueCandidates + .Filter(vc => vc.ElementCandidates.Exists(ec => ec.IsUsed)) + .Map(vc => new SimValue( + vc.Name, + vc.ElementCandidates + .Filter(ec => ec.IsUsed) + .Map(ec => new SimElement(ec.Name, ec.IsIndependentVariable, ec.Unit, ec.Description)) + ) + ); + + var output = new SimOutput(values); + + var config = new SimConfig( + SimulationName, + SimulationDescription, + DateTime.UtcNow, + code, + input, + output + ); + + config.WriteToFile(_pathToContainingDirectory); + + var codeLines = _codeLines.ToArray(); + + foreach (var parameterCandidate in parameterCandidates) + { + var line = $"{parameterCandidate.SymbolInfo.Symbol} <- ${{{parameterCandidate.SymbolInfo.Symbol}}}"; + var index = parameterCandidate.SymbolInfo.LineNo - 1; + codeLines[index] = line; + } + + var pathToRFile = Path.Combine(_pathToContainingDirectory, _codeFileName); + + File.WriteAllLines(pathToRFile, codeLines); + + return _simLibrary.Import(_pathToContainingDirectory); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + } + + try + { + Directory.Delete(_pathToContainingDirectory, true); + } + catch (Exception) { } + + _disposed = true; + } + } + + private void SetUpStaging() + { + var containingDirectoryName = DateTime.UtcNow.ToString("o", InvariantCulture).ToValidFileName(); + var pathToContainingDirectory = Path.Combine(Path.GetTempPath(), containingDirectoryName); + Directory.CreateDirectory(pathToContainingDirectory); + + var pathToRFile = Path.Combine(pathToContainingDirectory, _codeFileName); + + File.WriteAllLines(pathToRFile, _codeLines); + + _pathToContainingDirectory = pathToContainingDirectory; + } + + private readonly string _codeFileName; + private readonly Arr _codeLines; + private readonly SimLibrary _simLibrary; + private string _pathToContainingDirectory; + private bool _disposed = false; + } +} diff --git a/RVis.Model/Code/ParameterCandidate.cs b/RVis.Model/Code/ParameterCandidate.cs new file mode 100644 index 0000000..940c92f --- /dev/null +++ b/RVis.Model/Code/ParameterCandidate.cs @@ -0,0 +1,76 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System.Linq; +using System.Text.RegularExpressions; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public class ParameterCandidate + { + public static Arr CreateForExec(ISymbolInfo formal, Arr symbolInfos) + { + RequireTrue(formal.Value?.NColumns > 0); + RequireTrue(formal.Value.NRows == 1); + + var parameterCandidates = formal.Value.NumDataColumns + .Map(ndc => new ParameterCandidate(ndc.Name, ndc[0])) + .OrderBy(pc => pc.Name) + .ToArr(); + + foreach (var parameterCandidate in parameterCandidates) + { + if (parameterCandidate.Description.IsntAString() || parameterCandidate.Unit.IsntAString()) + { + var labelled = symbolInfos.Filter(si => si.Comment.IsAString() || si.Unit.IsAString()); + + var symbolInfo = labelled.Find(si => si.Symbol == parameterCandidate.Name); + + if (symbolInfo.IsNone) + { + var re = new Regex($"^{parameterCandidate.Name}\\W*=[^=]"); + symbolInfo = labelled.Find(si => re.IsMatch(si.Code)); + } + + symbolInfo.IfSome(si => + { + parameterCandidate.Description = parameterCandidate.Description ?? si.Comment; + parameterCandidate.Unit = parameterCandidate.Unit ?? si.Unit; + }); + } + } + + return parameterCandidates; + } + + private ParameterCandidate(string name, double value) + { + Name = name; + Value = value; + } + + public ParameterCandidate(ISymbolInfo symbolInfo) + { + RequireTrue(symbolInfo.Scalar.HasValue); + + SymbolInfo = symbolInfo; + + Name = symbolInfo.Symbol; + Value = symbolInfo.Scalar.Value; + Description = symbolInfo.Comment; + Unit = symbolInfo.Unit; + } + + public ISymbolInfo SymbolInfo { get; private set; } + + public string Name { get; } + + public double Value { get; } + + public string Description { get; set; } + + public string Unit { get; set; } + + public bool IsUsed { get; set; } + } +} diff --git a/RVis.Model/Code/SymbolInfo.cs b/RVis.Model/Code/SymbolInfo.cs new file mode 100644 index 0000000..101ac13 --- /dev/null +++ b/RVis.Model/Code/SymbolInfo.cs @@ -0,0 +1,47 @@ +using ProtoBuf; +using RVis.Data; +using System; + +namespace RVis.Model +{ + [Serializable] + [ProtoContract] + public class SymbolInfo : ISymbolInfo + { + [ProtoMember(1)] + public string Symbol { get; set; } + + [ProtoMember(2)] + public NumDataTable Value { get; set; } + + [ProtoMember(3)] + public int Length { get; set; } + + [ProtoMember(4)] + public string[] Names { get; set; } + + [ProtoMember(5)] + public SymbolType SymbolType { get; set; } + + [ProtoMember(6)] + public int SymbolicExpressionType { get; set; } + + [ProtoMember(7)] + public string Code { get; set; } + + [ProtoMember(8)] + public string Comment { get; set; } + + [ProtoMember(9)] + public string Unit { get; set; } + + [ProtoMember(10)] + public int Level { get; set; } + + [ProtoMember(11)] + public int LineNo { get; set; } + + [ProtoMember(12)] + public double? Scalar { get; set; } + } +} diff --git a/RVis.Model/Code/ValueCandidate.cs b/RVis.Model/Code/ValueCandidate.cs new file mode 100644 index 0000000..20fd7f7 --- /dev/null +++ b/RVis.Model/Code/ValueCandidate.cs @@ -0,0 +1,66 @@ +using LanguageExt; +using RVis.Data; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public class ValueCandidate + { + public static (ValueCandidate, Arr) CreateForExec(NumDataTable table, Arr symbolInfos) + { + RequireTrue(table.NColumns > 1); + RequireTrue(table.NumDataColumns[0].Length > 1); + + var valueCandidates = table.NumDataColumns + .Map(ndc => new ValueCandidate(ndc, symbolInfos)); + + var independentVariable = valueCandidates.Head(); + independentVariable.ElementCandidates[0].IsUsed = true; + independentVariable.ElementCandidates[0].IsIndependentVariable = true; + + valueCandidates = valueCandidates + .Tail() + .OrderBy(vc => vc.Name); + + return (independentVariable, valueCandidates.ToArr()); + } + + private ValueCandidate(NumDataColumn column, Arr symbolInfos) + { + Name = column.Name; + + ElementCandidates = Array( + new ElementCandidate(column.Name, column.Data.ToArr(), symbolInfos) + ); + } + + public ValueCandidate(ISymbolInfo symbolInfo, Arr symbolInfos) + { + RequireTrue(symbolInfo.Value?.NColumns > 0); + + SymbolInfo = symbolInfo; + + Name = symbolInfo.Symbol; + + ElementCandidates = symbolInfo.Value.NumDataColumns + .Map(ndc => new ElementCandidate(ndc.Name, ndc.Data.ToArr(), symbolInfos)) + .OrderBy(ec => ec.Name) + .ToArr(); + + if (ElementCandidates.Count == 1) + { + var elementCandidate = ElementCandidates.Head(); + elementCandidate.Description = elementCandidate.Description ?? symbolInfo.Comment; + elementCandidate.Unit = elementCandidate.Unit ?? symbolInfo.Unit; + } + } + + public ISymbolInfo SymbolInfo { get; } + + public string Name { get; } + + public Arr ElementCandidates { get; } + } +} diff --git a/RVis.Model/Distribution/BetaDistribution.cs b/RVis.Model/Distribution/BetaDistribution.cs new file mode 100644 index 0000000..9e90d2b --- /dev/null +++ b/RVis.Model/Distribution/BetaDistribution.cs @@ -0,0 +1,246 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; +using BetaDist = MathNet.Numerics.Distributions.Beta; + +namespace RVis.Model +{ + public struct BetaDistribution : IDistribution, IEquatable + { + public readonly static BetaDistribution Default = new BetaDistribution(NaN, NaN); + + public BetaDistribution(double alpha, double beta, double lower = NegativeInfinity, double upper = PositiveInfinity) + { + RequireTrue(lower < upper, "Expecting lower < upper"); + + Alpha = alpha; + Beta = beta; + Lower = lower; + Upper = upper; + } + + public double Alpha { get; } + public double Beta { get; } + public double Lower { get; } + public double Upper { get; } + + public Beta Implementation => IsConfigured + ? new Beta(Alpha, Beta, Generator) + : default; + + public DistributionType DistributionType => DistributionType.Beta; + + public bool CanTruncate => true; + + public bool IsTruncated => !IsNegativeInfinity(Lower) || !IsPositiveInfinity(Upper); + + public bool IsConfigured => !IsNaN(Alpha) && !IsNaN(Beta); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + + if (!IsTruncated) + { + implementation.Samples(samples); + return; + } + + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var uniform = new ContinuousUniform(lowerP, upperP, Generator); + uniform.Samples(samples); + + for (var i = 0; i < samples.Length; ++i) + { + samples[i] = implementation.InverseCumulativeDistribution(samples[i]); + } + } + + public double GetSample() + { + RequireTrue(IsConfigured); + + if (!IsTruncated) return BetaDist.Sample(Generator, Alpha, Beta); + + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var sample = ContinuousUniform.Sample(Generator, lowerP, upperP); + return implementation.InverseCumulativeDistribution(sample); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + + if (IsTruncated) + { + value = Max(Lower, Min(value, Upper)); + } + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return BetaDist.InvCDF(Alpha, Beta, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + ("qbeta", Array(("shape1", Alpha), ("shape2", Beta))); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var alpha = Alpha; + var beta = Beta; + + var cdLower = BetaDist.InvCDF(alpha, beta, lowerP); + var cdUpper = BetaDist.InvCDF(alpha, beta, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(v => BetaDist.PDF(alpha, beta, v)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return BetaDist.PDF(Alpha, Beta, value); + } + + public override string ToString() => + SerializeDistribution( + DistributionType, + IsTruncated + ? Array(Alpha, Beta, Lower, Upper) + : Array(Alpha, Beta) + ); + + public string ToString(string variableName) + { + var alpha = IsNaN(Alpha) ? "?" : Alpha.ToString("G4", CurrentCulture); + var beta = IsNaN(Beta) ? "?" : Beta.ToString("G4", CurrentCulture); + + var distribution = $"{variableName} ~ Beta(α = {alpha}, β = {beta})"; + + if (!IsTruncated) return distribution; + + var interval = "[" + + (IsNegativeInfinity(Lower) ? "-∞" : Lower.ToString(InvariantCulture)) + + ", " + + (IsPositiveInfinity(Upper) ? "+∞" : Upper.ToString(InvariantCulture)) + + "]"; + + return $"{distribution} {interval}"; + } + + public bool Equals(BetaDistribution rhs) => + Alpha.Equals(rhs.Alpha) && Beta.Equals(rhs.Beta) && Lower.Equals(rhs.Lower) && Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is BetaDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Alpha.GetHashCode(); + hashCode = hashCode * -1521134295 + Beta.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(BetaDistribution left, BetaDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(BetaDistribution left, BetaDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count < 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double alpha)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double beta)) return None; + if (parts.Count != 4) return new BetaDistribution(alpha, beta); + if (!TryParse(parts[2], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[3], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new BetaDistribution(alpha, beta, lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/BetaScaledDistribution.cs b/RVis.Model/Distribution/BetaScaledDistribution.cs new file mode 100644 index 0000000..666b4e3 --- /dev/null +++ b/RVis.Model/Distribution/BetaScaledDistribution.cs @@ -0,0 +1,269 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct BetaScaledDistribution : IDistribution, IEquatable + { + public readonly static BetaScaledDistribution Default = new BetaScaledDistribution(NaN, NaN, NaN, NaN); + + public BetaScaledDistribution( + double alpha, + double beta, + double location, + double scale, + double lower = NegativeInfinity, + double upper = PositiveInfinity + ) + { + RequireTrue(lower < upper, "Expecting lower < upper"); + + Alpha = alpha; + Beta = beta; + Location = location; + Scale = scale; + Lower = lower; + Upper = upper; + } + + public double Alpha { get; } + public double Beta { get; } + public double Location { get; } + public double Scale { get; } + public double Lower { get; } + public double Upper { get; } + + public BetaScaled Implementation => IsConfigured + ? new BetaScaled(Alpha, Beta, Location, Scale, Generator) + : default; + + public DistributionType DistributionType => DistributionType.BetaScaled; + + public bool CanTruncate => true; + + public bool IsTruncated => !IsNegativeInfinity(Lower) || !IsPositiveInfinity(Upper); + + public bool IsConfigured => !(IsNaN(Alpha) || IsNaN(Beta) || IsNaN(Location) || IsNaN(Scale)); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + + if (!IsTruncated) + { + implementation.Samples(samples); + return; + } + + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var uniform = new ContinuousUniform(lowerP, upperP, Generator); + uniform.Samples(samples); + + for (var i = 0; i < samples.Length; ++i) + { + samples[i] = implementation.InverseCumulativeDistribution(samples[i]); + } + } + + public double GetSample() + { + RequireTrue(IsConfigured); + + if (!IsTruncated) return BetaScaled.Sample(Generator, Alpha, Beta, Location, Scale); + + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var sample = ContinuousUniform.Sample(Generator, lowerP, upperP); + return implementation.InverseCumulativeDistribution(sample); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + + if (IsTruncated) + { + value = Max(Lower, Min(value, Upper)); + } + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return BetaScaled.InvCDF(Alpha, Beta, Location, Scale, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + default; + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var alpha = Alpha; + var beta = Beta; + var location = Location; + var scale = Scale; + + var cdLower = BetaScaled.InvCDF(alpha, beta, location, scale, lowerP); + var cdUpper = BetaScaled.InvCDF(alpha, beta, location, scale, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(v => BetaScaled.PDF(alpha, beta, location, scale, v)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return BetaScaled.PDF(Alpha, Beta, Location, Scale, value); + } + + public override string ToString() => + SerializeDistribution( + DistributionType, + IsTruncated + ? Array(Alpha, Beta, Location, Scale, Lower, Upper) + : Array(Alpha, Beta, Location, Scale) + ); + + public string ToString(string variableName) + { + var alpha = IsNaN(Alpha) ? "?" : Alpha.ToString("G4", CurrentCulture); + var beta = IsNaN(Beta) ? "?" : Beta.ToString("G4", CurrentCulture); + var location = IsNaN(Location) ? "?" : Location.ToString("G4", CurrentCulture); + var scale = IsNaN(Scale) ? "?" : Scale.ToString("G4", CurrentCulture); + + var distribution = $"{variableName} ~ Beta(α = {alpha}, β = {beta}, µ = {location}, σ = {scale})"; + + if (!IsTruncated) return distribution; + + var interval = "[" + + (IsNegativeInfinity(Lower) ? "-∞" : Lower.ToString(InvariantCulture)) + + ", " + + (IsPositiveInfinity(Upper) ? "+∞" : Upper.ToString(InvariantCulture)) + + "]"; + + return $"{distribution} {interval}"; + } + + public bool Equals(BetaScaledDistribution rhs) => + Alpha.Equals(rhs.Alpha) && + Beta.Equals(rhs.Beta) && + Location.Equals(rhs.Location) && + Scale.Equals(rhs.Scale) && + Lower.Equals(rhs.Lower) && + Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is BetaScaledDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Alpha.GetHashCode(); + hashCode = hashCode * -1521134295 + Beta.GetHashCode(); + hashCode = hashCode * -1521134295 + Location.GetHashCode(); + hashCode = hashCode * -1521134295 + Scale.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(BetaScaledDistribution left, BetaScaledDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(BetaScaledDistribution left, BetaScaledDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count < 4) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double alpha)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double beta)) return None; + if (!TryParse(parts[2], NumberStyles.Any, InvariantCulture, out double location)) return None; + if (!TryParse(parts[3], NumberStyles.Any, InvariantCulture, out double scale)) return None; + if (parts.Count != 6) return new BetaScaledDistribution(alpha, beta, location, scale); + if (!TryParse(parts[4], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[5], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new BetaScaledDistribution(alpha, beta, location, scale, lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/Distribution.cs b/RVis.Model/Distribution/Distribution.cs new file mode 100644 index 0000000..40ce2fa --- /dev/null +++ b/RVis.Model/Distribution/Distribution.cs @@ -0,0 +1,125 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Base.Extensions.EnumExt; +using static RVis.Model.Logger; +using static System.Globalization.CultureInfo; +using static System.String; + +namespace RVis.Model +{ + public static class Distribution + { + public static Arr GetDistributionTypes(DistributionType flags) => + _distributionTypes.Filter(dt => flags.HasFlag(dt)); + + public static string[] SerializeDistributions(Arr distributions) + { + RequireTrue(_distributionTypes.ForAll( + dt => distributions.Exists(ds => ds.DistributionType == dt) + )); + + return distributions.Map(ds => ds.ToString()).ToArray(); + } + + public static Arr DeserializeDistributions(string[] serialized) + { + var distributions = serialized + .Select(DeserializeDistribution) + .Somes() + .ToArr(); + + RequireUniqueElements(distributions, s => s.DistributionType); + + return _distributionTypes + .Map(dt => distributions + .Find(s => s.DistributionType == dt) + .Match(s => s, () => GetDefault(dt)) + ) + .ToArr(); + } + + public static Option DeserializeDistribution(string serialized) + { + if (serialized.IsntAString()) return None; + + var parts = serialized + .Split(new[] { PROP_SEP }, StringSplitOptions.RemoveEmptyEntries); + + if (!Enum.TryParse(parts[0], out DistributionType distributionType)) + { + Log.Error($"Unrecognised distribution type: {parts[0]}"); + return None; + } + + var parseMethod = _distributionParserMap[distributionType]; + + var maybeDistribution = (Option)parseMethod.Invoke( + null, + new object[] { parts.Skip(1).ToArr() } + ); + + if (maybeDistribution.IsNone) + { + Log.Error($"Failed to deserialize distribution: {serialized}"); + } + + return maybeDistribution; + } + + public static string SerializeDistribution(DistributionType distributionType, Arr state) => + $"{distributionType.ToString()}{PROP_SEP}{Join(PROP_SEP, state.Map(o => Convert.ToString(o, InvariantCulture)))}"; + + public static IDistribution GetDefault(DistributionType distributionType) + { + RequireTrue(_distributionTypeMap.ContainsKey(distributionType)); + return _distributionDefaultMap[distributionType]; + } + + public static Arr GetDefaults() => + _distributionTypes.Map(dt => _distributionDefaultMap[dt]); + + static Distribution() + { + _distributionTypes = GetFlags(); + + _distributionTypeMap = _distributionTypes.ToDictionary( + dt => dt, + dt => typeof(Distribution) + .Assembly + .GetType($"{nameof(RVis)}.{nameof(Model)}.{dt}{nameof(Distribution)}") + ); + + _distributionDefaultMap = _distributionTypeMap + .ToDictionary( + kvp => kvp.Key, + kvp => + { + var defaultProperty = kvp.Value.GetField(nameof(NormalDistribution.Default), BindingFlags.Public | BindingFlags.Static); + RequireNotNull(defaultProperty, $"Default property not found on type {kvp.Value.Name}"); + return defaultProperty.GetValue(null) as IDistribution; + }); + + _distributionParserMap = _distributionTypeMap + .ToDictionary( + kvp => kvp.Key, + kvp => + { + var parseMethod = kvp.Value.GetMethod(nameof(NormalDistribution.Parse), BindingFlags.NonPublic | BindingFlags.Static); + RequireNotNull(parseMethod, $"Parse method not found on type {kvp.Value.Name}"); + return parseMethod; + }); + } + + private const string PROP_SEP = "$"; + private static readonly Arr _distributionTypes; + private static readonly IDictionary _distributionTypeMap; + private static readonly IDictionary _distributionDefaultMap; + private static readonly IDictionary _distributionParserMap; + } +} diff --git a/RVis.Model/Distribution/Enum.cs b/RVis.Model/Distribution/Enum.cs new file mode 100644 index 0000000..d5a289e --- /dev/null +++ b/RVis.Model/Distribution/Enum.cs @@ -0,0 +1,49 @@ +using System; + +namespace RVis.Model +{ + [Flags] + public enum DistributionType + { + None = 0b_00000000_00000000, + Invariant = 0b_00000000_00000001, + Normal = 0b_00000000_00000010, + LogNormal = 0b_00000000_00000100, + Uniform = 0b_00000000_00001000, + LogUniform = 0b_00000000_00010000, + Beta = 0b_00000000_00100000, + BetaScaled = 0b_00000000_01000000, + Gamma = 0b_00000000_10000000, + InverseGamma = 0b_00000001_00000000, + StudentT = 0b_00000010_00000000, + + All = + Invariant + | Normal + | LogNormal + | Uniform + | LogUniform + | Beta + | BetaScaled + | Gamma + | InverseGamma + | StudentT, + + RQuantileSignatureTypes = + Beta + | Gamma + | LogNormal + | Normal + | Uniform, + + InvTrfmSplngTypes = + Beta + | BetaScaled + | Gamma + | LogNormal + | LogUniform + | Normal + | StudentT + | Uniform + } +} diff --git a/RVis.Model/Distribution/GammaDistribution.cs b/RVis.Model/Distribution/GammaDistribution.cs new file mode 100644 index 0000000..6bca3c9 --- /dev/null +++ b/RVis.Model/Distribution/GammaDistribution.cs @@ -0,0 +1,245 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct GammaDistribution : IDistribution, IEquatable + { + public readonly static GammaDistribution Default = new GammaDistribution(NaN, NaN); + + public GammaDistribution(double alpha, double beta, double lower = NegativeInfinity, double upper = PositiveInfinity) + { + RequireTrue(lower < upper, "Expecting lower < upper"); + + Alpha = alpha; + Beta = beta; + Lower = lower; + Upper = upper; + } + + public double Alpha { get; } + public double Beta { get; } + public double Lower { get; } + public double Upper { get; } + + public Gamma Implementation => IsConfigured + ? new Gamma(Alpha, Beta, Generator) + : default; + + public DistributionType DistributionType => DistributionType.Gamma; + + public bool CanTruncate => true; + + public bool IsTruncated => !IsNegativeInfinity(Lower) || !IsPositiveInfinity(Upper); + + public bool IsConfigured => !IsNaN(Alpha) && !IsNaN(Beta); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + + if (!IsTruncated) + { + implementation.Samples(samples); + return; + } + + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var uniform = new ContinuousUniform(lowerP, upperP, Generator); + uniform.Samples(samples); + + for (var i = 0; i < samples.Length; ++i) + { + samples[i] = implementation.InverseCumulativeDistribution(samples[i]); + } + } + + public double GetSample() + { + RequireTrue(IsConfigured); + + if (!IsTruncated) return Gamma.Sample(Generator, Alpha, Beta); + + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var sample = ContinuousUniform.Sample(Generator, lowerP, upperP); + return implementation.InverseCumulativeDistribution(sample); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + + if (IsTruncated) + { + value = Max(Lower, Min(value, Upper)); + } + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return Gamma.InvCDF(Alpha, Beta, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + ("qgamma", Array(("shape", Alpha), ("rate", Beta))); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var alpha = Alpha; + var beta = Beta; + + var cdLower = Gamma.InvCDF(alpha, beta, lowerP); + var cdUpper = Gamma.InvCDF(alpha, beta, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(v => Gamma.PDF(alpha, beta, v)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return Gamma.PDF(Alpha, Beta, value); + } + + public override string ToString() => + SerializeDistribution( + DistributionType, + IsTruncated + ? Array(Alpha, Beta, Lower, Upper) + : Array(Alpha, Beta) + ); + + public string ToString(string variableName) + { + var alpha = IsNaN(Alpha) ? "?" : Alpha.ToString("G4", CurrentCulture); + var beta = IsNaN(Beta) ? "?" : Beta.ToString("G4", CurrentCulture); + + var distribution = $"{variableName} ~ Γ(α = {alpha}, β = {beta})"; + + if (!IsTruncated) return distribution; + + var interval = "[" + + (IsNegativeInfinity(Lower) ? "-∞" : Lower.ToString(InvariantCulture)) + + ", " + + (IsPositiveInfinity(Upper) ? "+∞" : Upper.ToString(InvariantCulture)) + + "]"; + + return $"{distribution} {interval}"; + } + + public bool Equals(GammaDistribution rhs) => + Alpha.Equals(rhs.Alpha) && Beta.Equals(rhs.Beta) && Lower.Equals(rhs.Lower) && Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is GammaDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Alpha.GetHashCode(); + hashCode = hashCode * -1521134295 + Beta.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(GammaDistribution left, GammaDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(GammaDistribution left, GammaDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count < 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double alpha)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double beta)) return None; + if (parts.Count != 4) return new GammaDistribution(alpha, beta); + if (!TryParse(parts[2], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[3], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new GammaDistribution(alpha, beta, lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/Interface.cs b/RVis.Model/Distribution/Interface.cs new file mode 100644 index 0000000..9897857 --- /dev/null +++ b/RVis.Model/Distribution/Interface.cs @@ -0,0 +1,31 @@ +using LanguageExt; + +namespace RVis.Model +{ + public interface IDistribution + { + DistributionType DistributionType { get; } + bool CanTruncate { get; } + bool IsTruncated { get; } + bool IsConfigured { get; } + double Mean { get; } + double Variance { get; } + (double LowerP, double UpperP) CumulativeDistributionAtBounds { get; } + void FillSamples(double[] samples); + double GetSample(); + double GetProposal(double value, double step); + double ApplyBias(double step, double bias); + double InverseCumulativeDistribution(double p); + (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature { get; } + (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature { get; } + (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities); + double GetDensity(double value); + string ToString(); + string ToString(string variableName); + } + + public interface IDistribution : IDistribution + { + T Implementation { get; } + } +} diff --git a/RVis.Model/Distribution/InvariantDistribution.cs b/RVis.Model/Distribution/InvariantDistribution.cs new file mode 100644 index 0000000..e943e27 --- /dev/null +++ b/RVis.Model/Distribution/InvariantDistribution.cs @@ -0,0 +1,133 @@ +using LanguageExt; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static System.Double; +using static System.Globalization.CultureInfo; + +namespace RVis.Model +{ + public struct InvariantDistribution : IDistribution, IEquatable + { + public readonly static InvariantDistribution Default = new InvariantDistribution(NaN); + + public InvariantDistribution(double value) + => Value = value; + + public double Value { get; } + + public DistributionType DistributionType => DistributionType.Invariant; + + public bool CanTruncate => false; + + public bool IsTruncated => false; + + public bool IsConfigured => !IsNaN(Value); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Value; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return 0d; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds => + throw new InvalidOperationException("Not a continuous random variable"); + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + for (var i = 0; i < samples.Length; ++i) samples[i] = Value; + } + + public double GetSample() + { + RequireTrue(IsConfigured); + return Value; + } + + public double GetProposal(double value, double step) => + value; + + public double ApplyBias(double step, double bias) => + step; + + public double InverseCumulativeDistribution(double p) => + throw new InvalidOperationException("Not a continuous random variable"); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + default; + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature => + default; + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) => + throw new InvalidOperationException("Not a continuous random variable"); + + public double GetDensity(double value) => + throw new InvalidOperationException("Not a continuous random variable"); + + public override string ToString() => + SerializeDistribution(DistributionType, Array(Value)); + + public string ToString(string variableName) + { + var value = IsNaN(Value) ? "?" : Value.ToString("G4", CurrentCulture); + + return $"{variableName} = {value}"; + } + + public bool Equals(InvariantDistribution rhs) => + Value.Equals(rhs.Value); + + public override bool Equals(object obj) + { + if (obj is InvariantDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -774459404; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Value.GetHashCode(); + return hashCode; + } + + public static bool operator ==(InvariantDistribution left, InvariantDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(InvariantDistribution left, InvariantDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count != 1) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double value)) return None; + return new InvariantDistribution(value); + } + } +} diff --git a/RVis.Model/Distribution/InverseGammaDistribution.cs b/RVis.Model/Distribution/InverseGammaDistribution.cs new file mode 100644 index 0000000..e5e7b72 --- /dev/null +++ b/RVis.Model/Distribution/InverseGammaDistribution.cs @@ -0,0 +1,155 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; + +namespace RVis.Model +{ + public struct InverseGammaDistribution : IDistribution, IEquatable + { + public readonly static InverseGammaDistribution Default = new InverseGammaDistribution(NaN, NaN); + + public InverseGammaDistribution(double alpha, double beta) + { + Alpha = alpha; + Beta = beta; + } + + public double Alpha { get; } + public double Beta { get; } + + public InverseGamma Implementation => IsConfigured + ? new InverseGamma(Alpha, Beta, Generator) + : default; + + public DistributionType DistributionType => DistributionType.InverseGamma; + + public bool CanTruncate => false; + + public bool IsTruncated => false; + + public bool IsConfigured => !IsNaN(Alpha) && !IsNaN(Beta); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + => throw new InvalidOperationException("Operation not supported"); + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + implementation.Samples(samples); + } + + public double GetSample() + { + RequireTrue(IsConfigured); + return InverseGamma.Sample(Generator, Alpha, Beta); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) => + throw new InvalidOperationException("Not a continuous random variable"); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + (default, default); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature => + default; + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) => + throw new InvalidOperationException("Not a continuous random variable"); + + public double GetDensity(double value) => + throw new InvalidOperationException("Not a continuous random variable"); + + public override string ToString() => + SerializeDistribution(DistributionType, Array(Alpha, Beta)); + + public string ToString(string variableName) + { + var alpha = IsNaN(Alpha) ? "?" : Alpha.ToString("G4", CurrentCulture); + var beta = IsNaN(Beta) ? "?" : Beta.ToString("G4", CurrentCulture); + + return $"{variableName} ~ Inv-Gamma(α = {alpha}, β = {beta})"; + } + + public bool Equals(InverseGammaDistribution rhs) => + Alpha.Equals(rhs.Alpha) && Beta.Equals(rhs.Beta); + + public override bool Equals(object obj) + { + if (obj is InverseGammaDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Alpha.GetHashCode(); + hashCode = hashCode * -1521134295 + Beta.GetHashCode(); + return hashCode; + } + + public static bool operator ==(InverseGammaDistribution left, InverseGammaDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(InverseGammaDistribution left, InverseGammaDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count != 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double alpha)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double beta)) return None; + return new InverseGammaDistribution(alpha, beta); + } + } +} diff --git a/RVis.Model/Distribution/LogNormalDistribution.cs b/RVis.Model/Distribution/LogNormalDistribution.cs new file mode 100644 index 0000000..17a7829 --- /dev/null +++ b/RVis.Model/Distribution/LogNormalDistribution.cs @@ -0,0 +1,246 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct LogNormalDistribution : IDistribution, IEquatable + { + public readonly static LogNormalDistribution Default = new LogNormalDistribution(NaN, NaN); + + public LogNormalDistribution(double mu, double sigma, double lower = NegativeInfinity, double upper = PositiveInfinity) + { + RequireTrue(IsNaN(sigma) || sigma > 0, "Expecting σ > 0"); + RequireTrue(lower < upper, "Expecting lower < upper"); + + Mu = mu; + Sigma = sigma; + Lower = lower; + Upper = upper; + } + + public double Mu { get; } + public double Sigma { get; } + public double Lower { get; } + public double Upper { get; } + + public LogNormal Implementation => IsConfigured + ? new LogNormal(Mu, Sigma, Generator) + : default; + + public DistributionType DistributionType => DistributionType.LogNormal; + + public bool CanTruncate => true; + + public bool IsTruncated => !IsNegativeInfinity(Lower) || !IsPositiveInfinity(Upper); + + public bool IsConfigured => !IsNaN(Mu) && !IsNaN(Sigma); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Exp(Lower)); + var upperP = implementation.CumulativeDistribution(Exp(Upper)); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + + if (!IsTruncated) + { + implementation.Samples(samples); + return; + } + + var lowerP = implementation.CumulativeDistribution(Exp(Lower)); + var upperP = implementation.CumulativeDistribution(Exp(Upper)); + var uniform = new ContinuousUniform(lowerP, upperP, Generator); + uniform.Samples(samples); + + for (var i = 0; i < samples.Length; ++i) + { + samples[i] = implementation.InverseCumulativeDistribution(samples[i]); + } + } + + public double GetSample() + { + RequireTrue(IsConfigured); + + if (!IsTruncated) return LogNormal.Sample(Generator, Mu, Sigma); + + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Exp(Lower)); + var upperP = implementation.CumulativeDistribution(Exp(Upper)); + var sample = ContinuousUniform.Sample(Generator, lowerP, upperP); + return implementation.InverseCumulativeDistribution(sample); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + + if (IsTruncated) + { + value = Max(Exp(Lower), Min(value, Exp(Upper))); + } + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return LogNormal.InvCDF(Mu, Sigma, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + ("qlnorm", Array(("mean", Mu), ("sd", Sigma))); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var mu = Mu; + var sigma = Sigma; + + var cdLower = LogNormal.InvCDF(mu, sigma, lowerP); + var cdUpper = LogNormal.InvCDF(mu, sigma, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(v => LogNormal.PDF(mu, sigma, v)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return LogNormal.PDF(Mu, Sigma, value); + } + + public override string ToString() => + SerializeDistribution( + DistributionType, + IsTruncated + ? Array(Mu, Sigma, Lower, Upper) + : Array(Mu, Sigma) + ); + + public string ToString(string variableName) + { + var mean = IsNaN(Mu) ? "?" : Mu.ToString("G4", CurrentCulture); + var variance = IsNaN(Sigma) ? "?" : (Sigma * Sigma).ToString("G4", CurrentCulture); + + var distribution = $"ln({variableName}) ~ N(µ = {mean}, σ² = {variance})"; + + if (!IsTruncated) return distribution; + + var interval = "[" + + (IsNegativeInfinity(Lower) ? "-∞" : Lower.ToString(InvariantCulture)) + + ", " + + (IsPositiveInfinity(Upper) ? "+∞" : Upper.ToString(InvariantCulture)) + + "]"; + + return $"{distribution} {interval}"; + } + + public bool Equals(LogNormalDistribution rhs) => + Mu.Equals(rhs.Mu) && Sigma.Equals(rhs.Sigma) && Lower.Equals(rhs.Lower) && Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is LogNormalDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Mu.GetHashCode(); + hashCode = hashCode * -1521134295 + Sigma.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(LogNormalDistribution left, LogNormalDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(LogNormalDistribution left, LogNormalDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count < 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double mu)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double sigma)) return None; + if (parts.Count != 4) return new LogNormalDistribution(mu, sigma); + if (!TryParse(parts[2], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[3], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new LogNormalDistribution(mu, sigma, lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/LogUniformDistribution.cs b/RVis.Model/Distribution/LogUniformDistribution.cs new file mode 100644 index 0000000..61f52e9 --- /dev/null +++ b/RVis.Model/Distribution/LogUniformDistribution.cs @@ -0,0 +1,196 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct LogUniformDistribution : IDistribution, IEquatable + { + public readonly static LogUniformDistribution Default = new LogUniformDistribution(NaN, NaN); + + public LogUniformDistribution(double lower, double upper) + { + RequireTrue(IsNaN(lower) || lower < upper, "Expecting lower < upper"); + + Lower = lower; + Upper = upper; + } + + public double Lower { get; } + public double Upper { get; } + + public ContinuousUniform Implementation => IsConfigured + ? new ContinuousUniform(Lower, Upper, Generator) + : default; + + public DistributionType DistributionType => DistributionType.LogUniform; + + public bool CanTruncate => false; + + public bool IsTruncated => false; + + public bool IsConfigured => !IsNaN(Lower) && !IsNaN(Upper); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Exp(Implementation.Mean); + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Exp(Implementation.Variance); + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + implementation.Samples(samples); + for (var i = 0; i < samples.Length; ++i) samples[i] = Exp(samples[i]); + } + + public double GetSample() + { + RequireTrue(IsConfigured); + return Exp(ContinuousUniform.Sample(Generator, Lower, Upper)); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Exp(Implementation.Variance); + + var sample = GetSample(); + value += (sample - value) * step; + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return Exp(ContinuousUniform.InvCDF(Lower, Upper, p)); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + default; + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var lower = Lower; + var upper = Upper; + + var cdLower = ContinuousUniform.InvCDF(lower, upper, lowerP); + var cdUpper = ContinuousUniform.InvCDF(lower, upper, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(x => ContinuousUniform.PDF(lower, upper, x)); + values = values.Map(Exp); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return ContinuousUniform.PDF(Lower, Upper, Log(value)); + } + + public override string ToString() => + SerializeDistribution(DistributionType, Array(Lower, Upper)); + + public string ToString(string variableName) + { + var lower = IsNaN(Lower) ? "?" : Lower.ToString("G4", CurrentCulture); + var upper = IsNaN(Upper) ? "?" : Upper.ToString("G4", CurrentCulture); + + return $"ln({variableName}) ~ U(a = {lower}, b = {upper})"; + } + + public bool Equals(LogUniformDistribution rhs) => + Lower.Equals(rhs.Lower) && Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is LogUniformDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -774459404; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(LogUniformDistribution left, LogUniformDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(LogUniformDistribution left, LogUniformDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count != 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new LogUniformDistribution(lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/NormalDistribution.cs b/RVis.Model/Distribution/NormalDistribution.cs new file mode 100644 index 0000000..bf8e0d3 --- /dev/null +++ b/RVis.Model/Distribution/NormalDistribution.cs @@ -0,0 +1,255 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using RVis.Base.Extensions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct NormalDistribution : IDistribution, IEquatable + { + public readonly static NormalDistribution Default = new NormalDistribution(NaN, NaN); + + public static NormalDistribution CreateDefault(double mu) + { + var sigma = mu == 0d ? 1d : (Abs(mu) / 5d).ToSigFigs(1); + var lower = (mu - 2d * sigma).ToSigFigs(2); + var upper = (mu + 2d * sigma).ToSigFigs(2); + return new NormalDistribution(mu, sigma, lower, upper); + } + + public NormalDistribution(double mu, double sigma, double lower = NegativeInfinity, double upper = PositiveInfinity) + { + RequireTrue(IsNaN(sigma) || sigma > 0, "Expecting σ > 0"); + RequireTrue(lower < upper, "Expecting lower < upper"); + RequireTrue(IsNaN(mu) || mu >= lower, "µ out of bounds"); + RequireTrue(IsNaN(mu) || mu <= upper, "µ out of bounds"); + + Mu = mu; + Sigma = sigma; + Lower = lower; + Upper = upper; + } + + public double Mu { get; } + public double Sigma { get; } + public double Lower { get; } + public double Upper { get; } + + public Normal Implementation => IsConfigured + ? new Normal(Mu, Sigma, Generator) + : default; + + public DistributionType DistributionType => DistributionType.Normal; + + public bool CanTruncate => true; + + public bool IsTruncated => !IsNegativeInfinity(Lower) || !IsPositiveInfinity(Upper); + + public bool IsConfigured => !IsNaN(Mu) && !IsNaN(Sigma); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + + if (!IsTruncated) + { + implementation.Samples(samples); + return; + } + + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var uniform = new ContinuousUniform(lowerP, upperP, Generator); + uniform.Samples(samples); + + for (var i = 0; i < samples.Length; ++i) + { + samples[i] = implementation.InverseCumulativeDistribution(samples[i]); + } + } + + public double GetSample() + { + RequireTrue(IsConfigured); + + if (!IsTruncated) return Normal.Sample(Generator, Mu, Sigma); + + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var sample = ContinuousUniform.Sample(Generator, lowerP, upperP); + return implementation.InverseCumulativeDistribution(sample); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + value += Normal.Sample(Generator, 0, 1) * step; + + if (IsTruncated) + { + value = Max(Lower, Min(value, Upper)); + } + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return Normal.InvCDF(Mu, Sigma, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + ("qnorm", Array(("mean", Mu), ("sd", Sigma))); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var mu = Mu; + var sigma = Sigma; + + var cdLower = Normal.InvCDF(mu, sigma, lowerP); + var cdUpper = Normal.InvCDF(mu, sigma, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(v => Normal.PDF(mu, sigma, v)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return Normal.PDF(Mu, Sigma, value); + } + + public override string ToString() => + SerializeDistribution( + DistributionType, + IsTruncated + ? Array(Mu, Sigma, Lower, Upper) + : Array(Mu, Sigma) + ); + + public string ToString(string variableName) + { + var mean = IsNaN(Mu) ? "?" : Mu.ToString("G4", CurrentCulture); + var variance = IsNaN(Sigma) ? "?" : (Sigma * Sigma).ToString("G4", CurrentCulture); + var distribution = $"{variableName} ~ N(µ = {mean}, σ² = {variance})"; + + if (!IsTruncated) return distribution; + + var interval = "[" + + (IsNegativeInfinity(Lower) ? "-∞" : Lower.ToString(InvariantCulture)) + + ", " + + (IsPositiveInfinity(Upper) ? "+∞" : Upper.ToString(InvariantCulture)) + + "]"; + + return $"{distribution} {interval}"; + } + + public bool Equals(NormalDistribution rhs) => + Mu.Equals(rhs.Mu) && Sigma.Equals(rhs.Sigma) && Lower.Equals(rhs.Lower) && Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is NormalDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Mu.GetHashCode(); + hashCode = hashCode * -1521134295 + Sigma.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(NormalDistribution left, NormalDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(NormalDistribution left, NormalDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count < 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double mu)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double sigma)) return None; + if (parts.Count != 4) return new NormalDistribution(mu, sigma); + if (!TryParse(parts[2], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[3], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new NormalDistribution(mu, sigma, lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/RandomNumberGenerator.cs b/RVis.Model/Distribution/RandomNumberGenerator.cs new file mode 100644 index 0000000..91c1ec7 --- /dev/null +++ b/RVis.Model/Distribution/RandomNumberGenerator.cs @@ -0,0 +1,14 @@ +using MathNet.Numerics.Random; +using System; + +namespace RVis.Model +{ + public static class RandomNumberGenerator + { + public static Random Generator { get; private set; } = SystemRandomSource.Default; + + public static void ResetGenerator(int? seed = default) => Generator = seed.HasValue + ? new SystemRandomSource(seed.Value, true) + : SystemRandomSource.Default; + } +} diff --git a/RVis.Model/Distribution/StudentTDistribution.cs b/RVis.Model/Distribution/StudentTDistribution.cs new file mode 100644 index 0000000..0b431c4 --- /dev/null +++ b/RVis.Model/Distribution/StudentTDistribution.cs @@ -0,0 +1,261 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct StudentTDistribution : IDistribution, IEquatable + { + public readonly static StudentTDistribution Default = new StudentTDistribution(NaN, NaN, NaN); + + public DistributionType DistributionType => DistributionType.StudentT; + + public StudentTDistribution( + double mu, + double sigma, + double nu, + double lower = NegativeInfinity, + double upper = PositiveInfinity + ) + { + RequireTrue(lower < upper, "Expecting lower < upper"); + + Mu = mu; + Sigma = sigma; + Nu = nu; + Lower = lower; + Upper = upper; + } + + public double Mu { get; } + public double Sigma { get; } + public double Nu { get; } + public double Lower { get; } + public double Upper { get; } + + public StudentT Implementation => IsConfigured + ? new StudentT(Mu, Sigma, Nu, Generator) + : default; + + public bool CanTruncate => true; + + public bool IsTruncated => !IsNegativeInfinity(Lower) || !IsPositiveInfinity(Upper); + + public bool IsConfigured => !(IsNaN(Mu) || IsNaN(Sigma) || IsNaN(Nu)); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + + if (!IsTruncated) + { + implementation.Samples(samples); + return; + } + + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var uniform = new ContinuousUniform(lowerP, upperP, Generator); + uniform.Samples(samples); + + for (var i = 0; i < samples.Length; ++i) + { + samples[i] = implementation.InverseCumulativeDistribution(samples[i]); + } + } + + public double GetSample() + { + RequireTrue(IsConfigured); + + if (!IsTruncated) return StudentT.Sample(Generator, Mu, Sigma, Nu); + + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + var sample = ContinuousUniform.Sample(Generator, lowerP, upperP); + return implementation.InverseCumulativeDistribution(sample); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + + if (IsTruncated) + { + value = Max(Lower, Min(value, Upper)); + } + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return StudentT.InvCDF(Mu, Sigma, Nu, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + default; + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var mu = Mu; + var sigma = Sigma; + var nu = Nu; + + var cdLower = StudentT.InvCDF(mu, sigma, nu, lowerP); + var cdUpper = StudentT.InvCDF(mu, sigma, nu, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(v => StudentT.PDF(mu, sigma, nu, v)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return StudentT.PDF(Mu, Sigma, Nu, value); + } + + public override string ToString() => + SerializeDistribution( + DistributionType, + IsTruncated + ? Array(Mu, Sigma, Nu, Lower, Upper) + : Array(Mu, Sigma, Nu) + ); + + public string ToString(string variableName) + { + var location = IsNaN(Mu) ? "?" : Mu.ToString("G4", CurrentCulture); + var scale = IsNaN(Sigma) ? "?" : Sigma.ToString("G4", CurrentCulture); + var dof = IsNaN(Nu) ? "?" : Nu.ToString("G4", CurrentCulture); + + var distribution = $"{variableName} ~ T(µ = {location}, σ = {scale}, 𝜈 = {dof})"; + + if (!IsTruncated) return distribution; + + var interval = "[" + + (IsNegativeInfinity(Lower) ? "-∞" : Lower.ToString(InvariantCulture)) + + ", " + + (IsPositiveInfinity(Upper) ? "+∞" : Upper.ToString(InvariantCulture)) + + "]"; + + return $"{distribution} {interval}"; + } + + public bool Equals(StudentTDistribution rhs) => + Mu.Equals(rhs.Mu) && + Sigma.Equals(rhs.Sigma) && + Nu.Equals(rhs.Nu) && + Lower.Equals(rhs.Lower) && + Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is StudentTDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -1973367066; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Mu.GetHashCode(); + hashCode = hashCode * -1521134295 + Sigma.GetHashCode(); + hashCode = hashCode * -1521134295 + Nu.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(StudentTDistribution left, StudentTDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(StudentTDistribution left, StudentTDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count < 3) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double mu)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double sigma)) return None; + if (!TryParse(parts[2], NumberStyles.Any, InvariantCulture, out double nu)) return None; + if (parts.Count != 5) return new StudentTDistribution(mu, sigma, nu); + if (!TryParse(parts[3], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[4], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new StudentTDistribution(mu, sigma, nu, lower, upper); + } + } +} diff --git a/RVis.Model/Distribution/UniformDistribution.cs b/RVis.Model/Distribution/UniformDistribution.cs new file mode 100644 index 0000000..42b7052 --- /dev/null +++ b/RVis.Model/Distribution/UniformDistribution.cs @@ -0,0 +1,202 @@ +using LanguageExt; +using MathNet.Numerics.Distributions; +using RVis.Base.Extensions; +using System; +using System.Globalization; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Distribution; +using static RVis.Model.RandomNumberGenerator; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.Math; + +namespace RVis.Model +{ + public struct UniformDistribution : IDistribution, IEquatable + { + public readonly static UniformDistribution Default = new UniformDistribution(NaN, NaN); + + public static UniformDistribution CreateDefault(double center) + { + var disp = center == 0d ? 1d : Abs(center) / 2d; + var lower = (center - disp).ToSigFigs(2); + var upper = (center + disp).ToSigFigs(2); + return new UniformDistribution(lower, upper); + } + + public UniformDistribution(double lower, double upper) + { + Lower = lower; + Upper = upper; + } + + public double Lower { get; } + public double Upper { get; } + + public ContinuousUniform Implementation => IsConfigured + ? new ContinuousUniform(Lower, Upper, Generator) + : default; + + public DistributionType DistributionType => DistributionType.Uniform; + + public bool CanTruncate => false; + + public bool IsTruncated => false; + + public bool IsConfigured => !IsNaN(Lower) && !IsNaN(Upper); + + public double Mean + { + get + { + RequireTrue(IsConfigured); + return Implementation.Mean; + } + } + + public double Variance + { + get + { + RequireTrue(IsConfigured); + return Implementation.Variance; + } + } + + public (double LowerP, double UpperP) CumulativeDistributionAtBounds + { + get + { + var implementation = Implementation; + var lowerP = implementation.CumulativeDistribution(Lower); + var upperP = implementation.CumulativeDistribution(Upper); + return (lowerP, upperP); + } + } + + public void FillSamples(double[] samples) + { + RequireTrue(IsConfigured); + RequireNotNull(samples); + + var implementation = Implementation; + implementation.Samples(samples); + } + + public double GetSample() + { + RequireTrue(IsConfigured); + return ContinuousUniform.Sample(Generator, Lower, Upper); + } + + public double GetProposal(double value, double step) + { + if (IsNaN(value)) value = Mean; + if (IsNaN(step)) step = Implementation.Variance; + + var sample = GetSample(); + value += (sample - value) * step; + value = Max(Lower, Min(value, Upper)); + + return value; + } + + public double ApplyBias(double step, double bias) => + step * bias; + + public double InverseCumulativeDistribution(double p) + { + RequireTrue(IsConfigured); + return ContinuousUniform.InvCDF(Lower, Upper, p); + } + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RQuantileSignature => + ("qunif", Array(("min", Lower), ("max", Upper))); + + public (string FunctionName, Arr<(string ArgName, double ArgValue)> FunctionParameters) RInverseTransformSamplingSignature + { + get + { + var (lowerP, upperP) = CumulativeDistributionAtBounds; + return ("qunif", Array(("min", lowerP), ("max", upperP))); ; + } + } + + public (Arr Values, Arr Densities) GetDensities(double lowerP, double upperP, int nDensities) + { + RequireTrue(IsConfigured); + RequireTrue(nDensities > 1); + + var lower = Lower; + var upper = Upper; + + var cdLower = ContinuousUniform.InvCDF(lower, upper, lowerP); + var cdUpper = ContinuousUniform.InvCDF(lower, upper, upperP); + + var division = (cdUpper - cdLower) / (nDensities - 1); + var values = Range(0, nDensities).Map(i => cdLower + i * division).ToArr(); + var densities = values.Map(x => ContinuousUniform.PDF(lower, upper, x)); + + return (values, densities); + } + + public double GetDensity(double value) + { + RequireTrue(IsConfigured); + + return ContinuousUniform.PDF(Lower, Upper, value); + } + + public override string ToString() => + SerializeDistribution(DistributionType, Array(Lower, Upper)); + + public string ToString(string variableName) + { + var lower = IsNaN(Lower) ? "?" : Lower.ToString("G4", CurrentCulture); + var upper = IsNaN(Upper) ? "?" : Upper.ToString("G4", CurrentCulture); + + return $"{variableName} ~ U(a = {lower}, b = {upper})"; + } + + public bool Equals(UniformDistribution rhs) => + Lower.Equals(rhs.Lower) && Upper.Equals(rhs.Upper); + + public override bool Equals(object obj) + { + if (obj is UniformDistribution rhs) + { + return Equals(rhs); + } + + return false; + } + + public override int GetHashCode() + { + var hashCode = -774459404; + hashCode = hashCode * -1521134295 + DistributionType.GetHashCode(); + hashCode = hashCode * -1521134295 + Lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Upper.GetHashCode(); + return hashCode; + } + + public static bool operator ==(UniformDistribution left, UniformDistribution right) + { + return left.Equals(right); + } + + public static bool operator !=(UniformDistribution left, UniformDistribution right) + { + return !(left == right); + } + + internal static Option Parse(Arr parts) + { + if (parts.Count != 2) return None; + if (!TryParse(parts[0], NumberStyles.Any, InvariantCulture, out double lower)) return None; + if (!TryParse(parts[1], NumberStyles.Any, InvariantCulture, out double upper)) return None; + return new UniformDistribution(lower, upper); + } + } +} diff --git a/RVis.Model/Extensions/DistributionExt.cs b/RVis.Model/Extensions/DistributionExt.cs new file mode 100644 index 0000000..33d9714 --- /dev/null +++ b/RVis.Model/Extensions/DistributionExt.cs @@ -0,0 +1,31 @@ +using LanguageExt; +using RVis.Base.Extensions; +using static RVis.Base.Check; + +namespace RVis.Model.Extensions +{ + public static class DistributionExt + { + public static string ToStringIfSome(this Option maybeDistribution, string variableName) => + maybeDistribution.MatchUnsafe(d => d.ToString(variableName), () => null); + + public static string ToString(this Option maybeDistribution, string variableName) => + maybeDistribution.Match(d => d.ToString(variableName), () => DistributionType.None.ToString()); + + public static Arr SetDistribution(this Arr distributions, IDistribution distribution) + { + var index = distributions.FindIndex(d => d.DistributionType == distribution.DistributionType); + RequireTrue(index.IsFound()); + return distributions.SetItem(index, distribution); + } + + public static bool IsInverseTransformSamplingType(this IDistribution distribution) => + DistributionType.InvTrfmSplngTypes.HasFlag(distribution.DistributionType); + + public static bool HasRQuantileSignature(this IDistribution distribution) => + DistributionType.RQuantileSignatureTypes.HasFlag(distribution.DistributionType); + + public static bool RequiresInverseTransformSampling(this IDistribution distribution) => + distribution.IsInverseTransformSamplingType() && (!distribution.HasRQuantileSignature() || distribution.IsTruncated); + } +} diff --git a/RVis.Model/Extensions/SimDataExt.cs b/RVis.Model/Extensions/SimDataExt.cs new file mode 100644 index 0000000..4fff8a0 --- /dev/null +++ b/RVis.Model/Extensions/SimDataExt.cs @@ -0,0 +1,31 @@ +using RVis.Base.Extensions; +using System; + +namespace RVis.Model.Extensions +{ + public static class SimDataExt + { + public static bool IsResetEvent(this SimDataItem item) => + item.RequestToken.Resolve(out SimDataEvent @event) && @event == SimDataEvent.ServiceReset; + + public static bool IsSimDataEvent(this SimDataItem item, out SimDataEvent @event) => + item.RequestToken.Resolve(out @event); + + public static string GetDescription(this SimDataItem item) + { + if (item.RequestToken is string @string) + { + return @string; + } + + if (item.RequestToken is Enum @enum) + { + return @enum.GetDescription() ?? @enum.ToString(); + } + + if (item.Item is OutputRequest) return $"Output request for {item.Simulation.SimConfig.Title}"; + + return $"{item.Item.GetType().Name} for {item.Simulation.SimConfig.Title}"; + } + } +} diff --git a/RVis.Model/Extensions/SimDataLogExt.cs b/RVis.Model/Extensions/SimDataLogExt.cs new file mode 100644 index 0000000..f9fa201 --- /dev/null +++ b/RVis.Model/Extensions/SimDataLogExt.cs @@ -0,0 +1,10 @@ +using RVis.Base.Extensions; + +namespace RVis.Model.Extensions +{ + public static class SimDataLogExt + { + public static bool IsForOriginalConfig(this SimDataLogEntry logEntry) => + logEntry.ParameterAssignments.IsntAString(); + } +} diff --git a/RVis.Model/Extensions/SimEvidenceExt.cs b/RVis.Model/Extensions/SimEvidenceExt.cs new file mode 100644 index 0000000..673be31 --- /dev/null +++ b/RVis.Model/Extensions/SimEvidenceExt.cs @@ -0,0 +1,118 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Base.Extensions.LangExt; +using static RVis.Model.Logger; + +namespace RVis.Model.Extensions +{ + public static class SimEvidenceExt + { + public static SimEvidenceSource GetEvidenceSource(this ISimEvidence evidence, int id) => + evidence.EvidenceSources + .Find(es => es.ID == id) + .AssertSome($"Unknown evidence source: {id}"); + + public static string GetFQObservationsName(this SimObservations observations, SimEvidenceSource evidenceSource) => + observations.Subject != observations.RefName + ? $"{observations.Subject} x {observations.X.Count} from {evidenceSource.Name} ({observations.RefName})" + : $"{observations.Subject} x {observations.X.Count} from {evidenceSource.Name}"; + + public static string GetFQObservationsName(this ISimEvidence evidence, SimObservations observations) => + observations.GetFQObservationsName(evidence.GetEvidenceSource(observations.EvidenceSourceID)); + + public static bool ContainsObservations(this Arr arr, SimObservations observations) => + arr.Exists(o => o.EvidenceSourceID == observations.EvidenceSourceID && o.ID == observations.ID); + + public static bool ContainsObservationsSet(this Arr observationsSets, string subject) => + observationsSets.Exists(os => os.Subject == subject); + + public static Option FindObservationsSet(this Arr observationsSets, string subject) => + observationsSets.Find(os => os.Subject == subject); + + public static bool ContainsEvidenceSource(this Arr evidenceSources, string refHash) => + evidenceSources.Exists(es => es.RefHash == refHash); + + public static Option FindEvidenceSource(this Arr evidenceSources, string refHash) => + evidenceSources.Find(es => es.RefHash == refHash); + + public static Option FindEvidenceSource(this Arr evidenceSources, SimEvidenceSource evidenceSource) => + FindEvidenceSource(evidenceSources, evidenceSource.RefHash); + + public static bool IsSubject(this ISimEvidence evidence, string subject) => + subject.IsAString() && evidence.Subjects.Contains(subject); + + public static string GetReference(this SimEvidenceSource evidenceSource) => + $"{evidenceSource.ID}/{evidenceSource.RefName}/{evidenceSource.RefHash}"; + + public static string GetReference(this ISimEvidence evidence, SimObservations observations) + { + var evidenceSource = evidence.EvidenceSources + .Find(es => es.ID == observations.EvidenceSourceID) + .AssertSome(); + + return $"{evidenceSource.GetReference()}/{observations.ID}/{observations.Subject}/{observations.RefName}"; + } + + public static Option GetEvidenceSource(this ISimEvidence evidence, string reference) + { + try + { + var parts = reference.Split('/'); + RequireTrue(parts.Length == 3 || parts.Length == 6); + RequireTrue(int.TryParse(parts[0], out int evidenceSourceID)); + var evidenceSourceRefName = parts[1]; + RequireTrue(evidenceSourceRefName.IsAString()); + var evidenceSourceRefHash = parts[2]; + RequireTrue(evidenceSourceRefHash.IsAString()); + + return evidence.EvidenceSources + .Find(es => es.ID == evidenceSourceID && + es.RefName == evidenceSourceRefName && + es.RefHash == evidenceSourceRefHash + ); + } + catch (Exception ex) + { + Log.Error(ex, $"Invalid evidence source reference: {reference}"); + return None; + } + } + + public static Option GetObservations(this ISimEvidence evidence, string reference) + { + try + { + var parts = reference.Split('/'); + RequireTrue(parts.Length == 6); + + Option SomeEvidenceSource(SimEvidenceSource es) + { + RequireTrue(int.TryParse(parts[3], out int observationsID)); + var subject = parts[4]; + RequireTrue(subject.IsAString()); + var observationsRefName = parts[5]; + RequireTrue(observationsRefName.IsAString()); + + var observationsSet = evidence.GetObservationSet(subject); + return observationsSet.Observations.Find( + o => o.ID == observationsID && + o.RefName == observationsRefName && + o.EvidenceSourceID == es.ID + ); + } + + return evidence + .GetEvidenceSource(reference) + .Match(SomeEvidenceSource, NoneOf); + } + catch (Exception ex) + { + Log.Error(ex, $"Invalid observations reference: {reference}"); + return None; + } + } + } +} diff --git a/RVis.Model/Extensions/SimExt.Data.cs b/RVis.Model/Extensions/SimExt.Data.cs new file mode 100644 index 0000000..c718e37 --- /dev/null +++ b/RVis.Model/Extensions/SimExt.Data.cs @@ -0,0 +1,85 @@ +using LanguageExt; +using Nett; +using RVis.Base.Extensions; +using RVis.Data; +using System; +using System.IO; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Constant; + +namespace RVis.Model.Extensions +{ + public static partial class SimExt + { + public static bool HasData(this Simulation simulation, SimInput input) + { + var pathToData = GetPathToData(simulation, input.Hash); + return File.Exists(pathToData); + } + + public static NumDataTable LoadData(this Simulation simulation, SimInput input) + { + var pathToData = GetPathToData(simulation, input.Hash); + RequireFile(pathToData); + return NumDataTable.LoadFromBinaryFile(pathToData); + } + + public static void SaveData(this Simulation simulation, NumDataTable data, SimInput input) + { + var pathToData = GetPathToData(simulation, input.Hash); + if (File.Exists(pathToData)) + { + throw new InvalidOperationException($"Expecting empty path to save data: {pathToData}"); + } + + var pathToDataDirectory = Path.GetDirectoryName(pathToData); + if (!Directory.Exists(pathToDataDirectory)) Directory.CreateDirectory(pathToDataDirectory); + + NumDataTable.SaveToBinaryFile(data, pathToData); + + var edits = input.SimParameters.Filter(p => + { + var search = simulation.SimConfig.SimInput.SimParameters.Find(q => q.Name == p.Name); + var original = search.AssertSome($"Unknown parameter {p.Name}"); + return p.Value != original.Value; + }); + + if (!edits.IsEmpty) + { + var edited = simulation.SimConfig.SimInput.With(edits); + Toml.WriteFile(edited, Path.Combine(pathToDataDirectory, EditsFileName)); + } + } + + public static void SavePrivateData(this Simulation simulation, T data, string group, string category, string instance) + { + var path = GetPrivatePath( + simulation, + new[] { group, category, instance }, + PRIVATE_DATA_FILE_NAME + ); + + if (data == default) + { + if (File.Exists(path)) File.Delete(path); + return; + } + + Toml.WriteFile(data, path); + } + + public static Option LoadPrivateData(this Simulation simulation, string group, string category, string instance) + { + var path = GetPrivatePath( + simulation, + new[] { group, category, instance }, + PRIVATE_DATA_FILE_NAME + ); + + if (!File.Exists(path)) return None; + + return Toml.ReadFile(path); + } + } +} diff --git a/RVis.Model/Extensions/SimExt.Element.cs b/RVis.Model/Extensions/SimExt.Element.cs new file mode 100644 index 0000000..74e4d62 --- /dev/null +++ b/RVis.Model/Extensions/SimExt.Element.cs @@ -0,0 +1,31 @@ +using LanguageExt; +using RVis.Base.Extensions; +using static LanguageExt.Prelude; + +namespace RVis.Model.Extensions +{ + public static partial class SimExt + { + public static Option FindElement(this SimOutput output, string name) + { + foreach (var value in output.SimValues) + { + foreach (var element in value.SimElements) + { + if (element.Name == name) return Some(element); + } + } + + return None; + } + + public static Arr FindElementsWithUnit(this SimOutput output, string unit) => + output.SimValues.Bind(v => v.SimElements.Filter(e => e.Unit == unit)); + + public static string GetFQName(this SimElement element) => + element.Unit.IsAString() ? $"{element.Name} [{element.Unit}]" : element.Name; + + public static bool ContainsElement(this Arr values, string name) => + values.Exists(v => v.SimElements.Exists(e => e.Name == name)); + } +} diff --git a/RVis.Model/Extensions/SimExt.IO.cs b/RVis.Model/Extensions/SimExt.IO.cs new file mode 100644 index 0000000..9952f9e --- /dev/null +++ b/RVis.Model/Extensions/SimExt.IO.cs @@ -0,0 +1,50 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using System.IO; +using System.Linq; +using static RVis.Base.Check; +using static RVis.Model.Constant; +using static System.Globalization.CultureInfo; + +namespace RVis.Model.Extensions +{ + public static partial class SimExt + { + public static string GetPathToSessionLogDirectory(this Simulation simulation) => + simulation.GetPrivateDirectory( + new[] { "data", "log", DateTime.Now.ToString(FmtLogFilesDirectory, InvariantCulture) } + ); + + public static string GetPathToLogDirectory(this Simulation simulation) => + simulation.GetPrivateDirectory(new[] { "data", "log" }); + + public static string GetPathToSessionLog(this Simulation simulation) => + simulation.GetPrivatePath( + new[] { "data", "log", DateTime.Now.ToString(FmtLogFilesDirectory, InvariantCulture) }, + SessionLogFileName + ); + + public static string GetPrivateDirectory(this Simulation simulation, params string[] paths) + { + paths = paths.Select(c => c.ToValidFileName().ToLowerInvariant()).ToArray(); + RequireFalse(paths.Any(p => p.IsntAString())); + return Path.Combine(GetPathToPrivate(simulation), Path.Combine(paths)); + } + + public static string GetPrivatePath(this Simulation simulation, string[] paths, string fileName) + { + var directory = GetPrivateDirectory(simulation, paths); + return Path.Combine(directory, fileName); + } + + private static string GetPathToPrivate(this Simulation simulation) => + Path.Combine(simulation.PathToSimulation, PRIVATE_SUBDIRECTORY); + + public static string GetPathToDataDirectory(this Simulation simulation, string inputHash) => + Path.Combine(simulation.GetPathToPrivate(), DATA_SUBDIRECTORY, inputHash); + + private static string GetPathToData(this Simulation simulation, string inputHash) => + Path.Combine(simulation.GetPathToDataDirectory(inputHash), DATA_BIN_FILE_NAME); + } +} diff --git a/RVis.Model/Extensions/SimExt.Parameter.cs b/RVis.Model/Extensions/SimExt.Parameter.cs new file mode 100644 index 0000000..8c9bce3 --- /dev/null +++ b/RVis.Model/Extensions/SimExt.Parameter.cs @@ -0,0 +1,74 @@ +using LanguageExt; +using RVis.Base.Extensions; +using static RVis.Base.Check; +using static System.Double; +using static System.Globalization.CultureInfo; +using static System.String; + +namespace RVis.Model.Extensions +{ + public static partial class SimExt + { + public static string GetRValue(this SimParameter parameter) => + IsPositiveInfinity(parameter.Scalar) + ? "Inf" + : IsNegativeInfinity(parameter.Scalar) + ? "-Inf" + : IsNaN(parameter.Scalar) + ? "NaN" + : parameter.Scalar.ToString(InvariantCulture); + + public static Arr GetEdits(this SimInput @default, SimInput edited) => + @default.SimParameters + .Map(p => edited.SimParameters.Find(q => q.Name == p.Name && q.Value != p.Value)) + .Somes() + .ToArr(); + + public static string ToAssignment(this SimParameter parameter) => + $"{parameter.Name}={parameter.Value}{parameter.Unit ?? Empty}"; + + public static string ToAssignment(this SimParameter parameter, string valueFormat) => + $"{parameter.Name}=" + + $"{(IsNaN(parameter.Scalar) ? parameter.Value : parameter.Scalar.ToString(valueFormat, InvariantCulture))}" + + $"{parameter.Unit ?? Empty}"; + + public static string ToAssignments(this Arr edits) => + Join(";", edits.Map(e => e.ToAssignment())); + + public static string ToAssignments(this Arr edits, string valueFormat) => + Join(";", edits.Map(e => e.ToAssignment(valueFormat))); + + public static Arr GetParameters(this SimInput @default, string assignments) + { + if (assignments.IsntAString()) return Arr.Empty; + + SimParameter AssignmentToEdit(string assignment) + { + var parts = assignment.Split('='); + RequireTrue(parts.Length == 2, $"Invalid parameter assignment {assignment}"); + var name = parts[0].Trim(); + var parameter = @default.SimParameters.Find(p => p.Name == name).AssertSome($"Unknown parameter name {name}"); + var value = parts[1].Trim(); + if (parameter.Unit.IsAString() && value.EndsWith(parameter.Unit, System.StringComparison.InvariantCulture)) + { + value = value.Substring(0, value.Length - parameter.Unit.Length).TrimEnd(); + } + return parameter.With(value); + } + + return assignments.Split(';').Map(AssignmentToEdit).ToArr(); + } + + public static bool ContainsParameter(this Arr parameters, string name) => + parameters.Exists(p => p.Name == name); + + public static Option FindParameter(this Arr parameters, string name) => + parameters.Find(p => p.Name == name); + + public static Option FindParameter(this Arr parameters, SimParameter parameter) => + FindParameter(parameters, parameter.Name); + + public static SimParameter GetParameter(this Arr parameters, string name) => + parameters.Find(p => p.Name == name).AssertSome($"Unknown parameter: {name}"); + } +} diff --git a/RVis.Model/Extensions/SimExt.cs b/RVis.Model/Extensions/SimExt.cs new file mode 100644 index 0000000..03d5822 --- /dev/null +++ b/RVis.Model/Extensions/SimExt.cs @@ -0,0 +1,55 @@ +using LanguageExt; +using Nett; +using RVis.Base.Extensions; +using System.Diagnostics; +using System.IO; +using static LanguageExt.Prelude; +using static RVis.Model.Constant; +using static RVis.Model.Sim; + +namespace RVis.Model.Extensions +{ + public static partial class SimExt + { + internal static Option LoadConfig(this Simulation simulation, string inputHash) + { + if (simulation.SimConfig.SimInput.Hash == inputHash) return Some(simulation.SimConfig); + + var pathToData = GetPathToData(simulation, inputHash); + var pathToDataDirectory = Path.GetDirectoryName(pathToData); + if (!Directory.Exists(pathToDataDirectory)) return None; + + var pathToEditsFile = Path.Combine(pathToDataDirectory, EditsFileName); + if (!File.Exists(pathToEditsFile)) return None; + + var fromToml = Toml.ReadFile(pathToEditsFile); + + var edits = FromToml(fromToml); + + var input = simulation.SimConfig.SimInput.With(edits.SimParameters); + var config = simulation.SimConfig.With(input); + return Some(config); + } + + public static bool IsExecType(this Simulation simulation) => + simulation.SimConfig.SimCode.Exec.IsAString(); + + public static bool IsTmplType(this Simulation simulation) => + !simulation.IsExecType(); + + internal static void WriteToFile(this SimConfig config, string pathToSimulation) + { + var pathToPrivate = Path.Combine(pathToSimulation, PRIVATE_SUBDIRECTORY); + if (!Directory.Exists(pathToPrivate)) Directory.CreateDirectory(pathToPrivate); + + var pathToConfig = Path.Combine(pathToPrivate, CONFIG_FILE_NAME); + var toToml = ToToml(config); + Toml.WriteFile(toToml, pathToConfig); + } + + internal static readonly string SessionLogFileName = $"{Process.GetCurrentProcess().Id}.{LogFileExtension}"; + internal const string FmtLogFilesDirectory = "yyyy-MM-dd"; + internal const string LogFileExtension = "log"; + internal const string EditsFileName = "parameters.toml"; + } +} diff --git a/RVis.Model/Logger.cs b/RVis.Model/Logger.cs new file mode 100644 index 0000000..6f6691f --- /dev/null +++ b/RVis.Model/Logger.cs @@ -0,0 +1,10 @@ +using NLog; + +namespace RVis.Model +{ + internal static class Logger + { + internal static ILogger Log => _log ?? (_log = Base.Logging.Create($"{nameof(RVis)}{nameof(Model)}.All")); + private static ILogger _log; + } +} diff --git a/RVis.Model/R/Interface.cs b/RVis.Model/R/Interface.cs new file mode 100644 index 0000000..7fc1039 --- /dev/null +++ b/RVis.Model/R/Interface.cs @@ -0,0 +1,150 @@ +using LanguageExt; +using RVis.Data; +using System; +using System.Collections.Generic; +using System.ServiceModel; +using System.Threading; + +namespace RVis.Model +{ + public interface IRVisServerPool + { + Option RequestServer(); + + Option RenewServerLicense(ServerLicense serverLicense); + + WaitHandle SlotFree { get; } + + IObservable<(int ServerID, ServerLicense ServerLicense, bool HasExpired)> ServerLicenses { get; } + } + + public interface IRVisServer + { + int ID { get; } + + IRVisClient OpenChannel(); + + bool IsUp { get; } + + void Stop(); + } + + public interface IRVisClient : IDisposable + { + int ID { get; } + + (string Name, string Value)[] GetRversion(); + + (string Package, string Version)[] GetInstalledPackages(); + + void Clear(); + + void GarbageCollect(); + + void StopServer(); + + Dictionary EvaluateStrings(string code); + + Dictionary EvaluateDoubles(string code); + + NumDataColumn[] EvaluateNumData(string code); + + void EvaluateNonQuery(string code); + + void RunExec(string pathToCode, SimConfig config); + + void SourceFile(string pathToCode); + + NumDataTable TabulateExecOutput(SimConfig config); + + NumDataTable TabulateTmplOutput(SimConfig config); + + ISymbolInfo[] InspectSymbols(string pathToCode); + + byte[] Serialize(string objectName); + + void Unserialize(byte[] raw, string objectName); + + byte[] SaveObjectToBinary(string objectName); + + void LoadFromBinary(byte[] raw); + + void CreateVector(double[] source, string objectName); + } + + public interface IRVisServiceCallback + { + //[OperationContract(IsOneWay = true)] + //void NotifyResult(ResultType resultType); + + [OperationContract] + void NotifyGeneratedOutput(NumDataTable[] generatedOutput); + + [OperationContract] + void NotifyFault(string message, string innerMessage); + } + + [ServiceContract(CallbackContract = typeof(IRVisServiceCallback))] + public interface IRVisService + { + [OperationContract] + NameValueArraySvcRes GetRversion(); + + [OperationContract] + NameValueArraySvcRes GetInstalledPackages(); + + [OperationContract] + BoolSvcRes IsBusy(); + + [OperationContract] + UnitSvcRes Clear(); + + [OperationContract] + UnitSvcRes GarbageCollect(); + + [OperationContract] + UnitSvcRes Shutdown(); + + [OperationContract] + NameStringsMapSvcRes EvaluateStrings(string code); + + [OperationContract] + NameDoublesMapSvcRes EvaluateDoubles(string code); + + [OperationContract] + NumDataColumnArraySvcRes EvaluateNumData(string code); + + [OperationContract] + UnitSvcRes EvaluateNonQuery(string code); + + [OperationContract] + UnitSvcRes RunExec(string pathToCode, SimConfig config); + + [OperationContract] + UnitSvcRes SourceFile(string pathToCode); + + [OperationContract] + NumDataTableSvcRes TabulateExecOutput(SimConfig config); + + [OperationContract] + NumDataTableSvcRes TabulateTmplOutput(SimConfig config); + + [OperationContract] + SymbolInfoArraySvcRes InspectSymbols(string pathToCode); + + [OperationContract] + ByteArraySvcRes Serialize(string objectName); + + [OperationContract] + UnitSvcRes Unserialize(byte[] raw, string objectName); + + [OperationContract] + ByteArraySvcRes SaveObjectToBinary(string objectName); + + [OperationContract] + UnitSvcRes LoadFromBinary(byte[] raw); + + [OperationContract] + UnitSvcRes CreateVector(double[] source, string objectName); + } +} diff --git a/RVis.Model/R/RVisServerPool.cs b/RVis.Model/R/RVisServerPool.cs new file mode 100644 index 0000000..21acfc1 --- /dev/null +++ b/RVis.Model/R/RVisServerPool.cs @@ -0,0 +1,167 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Logger; + +namespace RVis.Model +{ + public sealed class RVisServerPool : IRVisServerPool, IDisposable + { + public RVisServerPool() + { + } + + public void CreatePool(Func serverFactory, int size) + { + RequireTrue(size >= 0); + RequireTrue(_serverSlots.IsEmpty); + + var servers = Range(0, size).Map(_ => serverFactory()).ToArr(); + var serverSlots = servers.Map(s => new ServerSlot(s) { IsFree = true }); + + lock (_syncLock) + { + _serverSlots = serverSlots; + } + + Log.Debug($"{nameof(RVisServerPool)} initialized {size} instances"); + } + + public void DestroyPool() => DestroyPool(disposing: false); + + public WaitHandle SlotFree => _mreServerSlots.WaitHandle; + + public Option RequestServer() + { + lock (_syncLock) + { + var freeServerSlot = _serverSlots.Find(s => s.IsFree); + + return freeServerSlot.Match( + serverSlot => + { + var client = serverSlot.Server.OpenChannel(); + var serverLicense = new ServerLicense(client, (sl) => ExpireLicense(serverSlot, sl)); + serverSlot.IsFree = false; + _serverLicenses.OnNext((serverSlot.Server.ID, serverLicense, HasExpired: false)); + + Log.Debug($"Issued license for server {serverSlot.Server.ID}"); + + return Some(serverLicense); + }, + () => + { + _mreServerSlots.Reset(); + return None; + }); + } + } + + public Option RenewServerLicense(ServerLicense serverLicense) + { + lock (_syncLock) + { + var serverSlot = _serverSlots + .Find(s => s.Server.ID == serverLicense.Client.ID) + .AssertSome($"Trying to renew license for defunct server {serverLicense.Client.ID}"); + + if (!serverSlot.IsFree) return None; + + var client = serverSlot.Server.OpenChannel(); + var renewedServerLicense = new ServerLicense(client, (sl) => ExpireLicense(serverSlot, sl)); + serverSlot.IsFree = false; + _serverLicenses.OnNext((serverSlot.Server.ID, renewedServerLicense, HasExpired: false)); + + Log.Debug($"Renewed license for server {serverSlot.Server.ID}"); + + return renewedServerLicense; + } + } + + public IObservable<(int ServerID, ServerLicense ServerLicense, bool HasExpired)> ServerLicenses => + _serverLicenses.AsObservable(); + private readonly Subject<(int ServerID, ServerLicense ServerLicense, bool HasExpired)> _serverLicenses = + new Subject<(int ServerID, ServerLicense ServerLicense, bool HasExpired)>(); + + public void Dispose() => Dispose(disposing: true); + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + DestroyPool(disposing); + _mreServerSlots.Dispose(); + _serverLicenses.Dispose(); + } + + _disposed = true; + } + } + + private void ExpireLicense(ServerSlot serverSlot, ServerLicense serverLicense) + { + lock (_syncLock) + { + _serverLicenses.OnNext((serverSlot.Server.ID, serverLicense, HasExpired: true)); + serverLicense.Client.Dispose(); + serverSlot.IsFree = true; + + if (_serverSlots.IndexOf(serverSlot).IsFound()) _mreServerSlots.Set(); + + Log.Debug($"Expired license for server {serverSlot.Server.ID}"); + } + } + + private void DestroyPool(bool disposing) + { + lock (_syncLock) + { + if (0 == _serverSlots.Count) return; + + var serverSlots = _serverSlots; + _serverSlots = default; + + serverSlots.Iter(ss => + { + if (!ss.IsFree) + _serverLicenses.OnNext( + (ss.Server.ID, ServerLicense.NullLicense, HasExpired: true) + ); + }); + + void StopDisposeServers() => + serverSlots.Map(ss => ss.Server).Iter(s => + { + s.Stop(); + (s as IDisposable).Dispose(); + }); + + if (disposing) + { + // shutting down so block + StopDisposeServers(); + } + else + { + // user api call so don't block + Task.Run(StopDisposeServers); + } + + Log.Debug($"{nameof(RVisServerPool)} destroyed pool"); + } + } + + private Arr _serverSlots; + private readonly object _syncLock = new object(); + private readonly ManualResetEventSlim _mreServerSlots = new ManualResetEventSlim(true); + private bool _disposed = false; + } +} diff --git a/RVis.Model/R/ServerLicense.cs b/RVis.Model/R/ServerLicense.cs new file mode 100644 index 0000000..82cdab0 --- /dev/null +++ b/RVis.Model/R/ServerLicense.cs @@ -0,0 +1,37 @@ +using System; + +namespace RVis.Model +{ + public sealed class ServerLicense : IDisposable + { + public readonly static ServerLicense NullLicense = new ServerLicense(); + + internal ServerLicense(IRVisClient client, Action expireLicense) + { + Client = client; + _expireLicense = expireLicense; + } + + public IRVisClient Client { get; } + + public void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _expireLicense(this); + } + + _disposed = true; + } + } + + public void Dispose() => Dispose(true); + + private ServerLicense() { } + + private bool _disposed = false; + private readonly Action _expireLicense; + } +} diff --git a/RVis.Model/R/ServerSlot.cs b/RVis.Model/R/ServerSlot.cs new file mode 100644 index 0000000..48d636d --- /dev/null +++ b/RVis.Model/R/ServerSlot.cs @@ -0,0 +1,9 @@ +namespace RVis.Model +{ + internal class ServerSlot + { + internal ServerSlot(IRVisServer server) => Server = server; + internal IRVisServer Server { get; } + internal bool IsFree { get; set; } + } +} diff --git a/RVis.Model/R/SvcRes.cs b/RVis.Model/R/SvcRes.cs new file mode 100644 index 0000000..d730936 --- /dev/null +++ b/RVis.Model/R/SvcRes.cs @@ -0,0 +1,178 @@ +using ProtoBuf; +using RVis.Data; +using System; +using System.Collections.Generic; + +namespace RVis.Model +{ + [ProtoContract] + public class UnitRes { } + + public static class SvcRes + { + public static readonly UnitSvcRes Unit = new UnitSvcRes(); + + internal static SvcRes Create(Exception exception) => + new SvcRes { Messages = ExceptionToMessages(exception) }; + + internal static SvcRes Create(T t) => + new SvcRes { Value = t }; + + internal static T Create(U u) where T : SvcRes, new() => + new T { Value = u }; + + internal static T Create(Exception exception) where T : SvcRes, new() => + new T { Messages = ExceptionToMessages(exception) }; + + internal static void AssertNoFault(string[] messages) + { + if (null == messages) return; + var exception = new InvalidOperationException(messages[0]); + exception.Data["Messages"] = messages; + throw exception; + } + + private static string[] ExceptionToMessages(Exception exception) + { + var messages = new List { exception.Message }; + + while (null != exception.InnerException) + { + exception = exception.InnerException; + messages.Add(exception.Message); + } + + return messages.ToArray(); + } + } + + [ProtoContract] + public class NameValueArraySvcRes : SvcRes<(string, string)[]> + { + public static implicit operator NameValueArraySvcRes((string, string)[] value) => + SvcRes.Create(value); + + public static implicit operator NameValueArraySvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class BoolSvcRes : SvcRes + { + public static implicit operator BoolSvcRes(bool value) => + SvcRes.Create(value); + + public static implicit operator BoolSvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class UnitSvcRes : SvcRes + { + public static implicit operator UnitSvcRes(UnitRes value) => + SvcRes.Create(value); + + public static implicit operator UnitSvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class NameDoublesMapSvcRes : SvcRes> + { + public static implicit operator NameDoublesMapSvcRes(Dictionary value) => + SvcRes.Create>(value); + + public static implicit operator NameDoublesMapSvcRes(Exception exception) => + SvcRes.Create>(exception); + } + + [ProtoContract] + public class NumDataColumnArraySvcRes : SvcRes + { + public static implicit operator NumDataColumnArraySvcRes(NumDataColumn[] value) => + SvcRes.Create(value); + + public static implicit operator NumDataColumnArraySvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class NumDataTableSvcRes : SvcRes + { + public static implicit operator NumDataTableSvcRes(NumDataTable value) => + SvcRes.Create(value); + + public static implicit operator NumDataTableSvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class SymbolInfoArraySvcRes : SvcRes + { + public static implicit operator SymbolInfoArraySvcRes(SymbolInfo[] value) => + SvcRes.Create(value); + + public static implicit operator SymbolInfoArraySvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class ByteArraySvcRes : SvcRes + { + public static implicit operator ByteArraySvcRes(byte[] value) => + SvcRes.Create(value); + + public static implicit operator ByteArraySvcRes(Exception exception) => + SvcRes.Create(exception); + } + + [ProtoContract] + public class NameStringsMapSvcRes : SvcRes> + { + public static implicit operator NameStringsMapSvcRes(Dictionary value) => + SvcRes.Create>(value); + + public static implicit operator NameStringsMapSvcRes(Exception exception) => + SvcRes.Create>(exception); + } + + [ProtoContract] + [ProtoInclude(3, typeof(NameValueArraySvcRes))] + [ProtoInclude(4, typeof(BoolSvcRes))] + [ProtoInclude(5, typeof(UnitSvcRes))] + [ProtoInclude(6, typeof(NameDoublesMapSvcRes))] + [ProtoInclude(7, typeof(NumDataColumnArraySvcRes))] + [ProtoInclude(8, typeof(NumDataTableSvcRes))] + [ProtoInclude(9, typeof(SymbolInfoArraySvcRes))] + [ProtoInclude(10, typeof(ByteArraySvcRes))] + [ProtoInclude(11, typeof(NameStringsMapSvcRes))] + public class SvcRes + { + [ProtoMember(1)] + public T Value { get; set; } + + [ProtoMember(2)] + public string[] Messages { get; set; } + + public void Void() => + AssertNoFault(); + + public T Return() + { + AssertNoFault(); + return Value; + } + + public static implicit operator SvcRes(T t) => + SvcRes.Create(t); + + public static implicit operator SvcRes(Exception exception) => + SvcRes.Create(exception); + + public static implicit operator T(SvcRes svcRes) => + svcRes.Return(); + + protected void AssertNoFault() => + SvcRes.AssertNoFault(Messages); + } +} diff --git a/RVis.Model/RVis.Model.csproj b/RVis.Model/RVis.Model.csproj new file mode 100644 index 0000000..49c8f6f --- /dev/null +++ b/RVis.Model/RVis.Model.csproj @@ -0,0 +1,59 @@ + + + + netstandard2.0;net471 + AnyCPU;x64 + + + + latest + TRACE;DEBUG + + + + latest + TRACE;DEBUG + + + + latest + + + + latest + + + + latest + TRACE;DEBUG + + + + latest + TRACE;DEBUG + + + + latest + + + + latest + + + + + + + + + + + + + + + + + + diff --git a/RVis.Model/Sim/Constant.cs b/RVis.Model/Sim/Constant.cs new file mode 100644 index 0000000..c2b0ad9 --- /dev/null +++ b/RVis.Model/Sim/Constant.cs @@ -0,0 +1,13 @@ +namespace RVis.Model +{ + internal static class Constant + { + internal const string PRIVATE_SUBDIRECTORY = ".rvis"; + internal const string CONFIG_FILE_NAME = "config.toml"; + internal const string DATA_SUBDIRECTORY = "data"; + internal const string DATA_BIN_FILE_NAME = "data.bin"; + internal const string PRIVATE_DATA_FILE_NAME = "data.toml"; + internal const string EVIDENCE_SUBDIRECTORY = "evidence"; + internal const string EVIDENCE_SOURCES_FILE_NAME = "sources.toml"; + } +} diff --git a/RVis.Model/Sim/EventArgs.cs b/RVis.Model/Sim/EventArgs.cs new file mode 100644 index 0000000..e82a6f0 --- /dev/null +++ b/RVis.Model/Sim/EventArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace RVis.Model +{ + public class SimulationDeletedEventArgs : EventArgs + { + public SimulationDeletedEventArgs(Simulation simulation) => + Simulation = simulation; + + public Simulation Simulation { get; } + } +} diff --git a/RVis.Model/Sim/Sim.cs b/RVis.Model/Sim/Sim.cs new file mode 100644 index 0000000..4b953f7 --- /dev/null +++ b/RVis.Model/Sim/Sim.cs @@ -0,0 +1,113 @@ +using Nett; +using RVis.Base.Extensions; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; + +namespace RVis.Model +{ + public static class Sim + { + public static void WriteConfigToFile(TSimConfig config, string pathToFile) => + Toml.WriteFile(config, pathToFile); + + public static TSimConfig ReadConfigFromFile(string pathToFile) => + Toml.ReadFile(pathToFile); + + public static void SerializeConfig(SimConfig config, Stream stream) => + new BinaryFormatter().Serialize(stream, config); + + public static SimConfig DeserializeConfig(Stream stream) => + (SimConfig)new BinaryFormatter().Deserialize(stream); + + internal static SimInput FromToml(TSimInput input) + { + var parameters = input.Parameters?.IsNullOrEmpty() == true + ? default + : input.Parameters + .Select(p => new SimParameter(p.Name, p.Value, p.Unit, p.Description)) + .ToArr(); + return new SimInput(parameters, input.IsDefault); + } + + internal static TSimConfig ToToml(SimConfig config) + { + return new TSimConfig + { + Title = config.Title, + Description = config.Description, + ImportedOn = config.ImportedOn, + Code = new TSimCode + { + File = config.SimCode.File, + Exec = config.SimCode.Exec, + Formal = config.SimCode.Formal + }, + Input = new TSimInput + { + Parameters = config.SimInput.SimParameters.IsEmpty + ? default + : config.SimInput.SimParameters.Map(p => new TSimParameter + { + Name = p.Name, + Value = p.Value, + Unit = p.Unit, + Description = p.Description + }).ToArray(), + IsDefault = config.SimInput.IsDefault + }, + Output = new TSimOutput + { + Values = config.SimOutput.SimValues.IsEmpty + ? default + : config.SimOutput.SimValues.Map(v => new TSimValue + { + Name = v.Name, + Elements = v.SimElements.Map(e => new TSimElement + { + Name = e.Name, + IsIndependentVariable = e.IsIndependentVariable, + Unit = e.Unit, + Description = e.Description + }).ToArray() + }).ToArray() + } + }; + } + + internal static SimConfig FromToml(TSimConfig config) + { + var code = new SimCode(config.Code?.File, config.Code?.Exec, config.Code?.Formal); + + var parameters = config.Input?.Parameters?.IsNullOrEmpty() == true + ? default + : config.Input.Parameters + .Select(p => new SimParameter(p.Name, p.Value, p.Unit, p.Description)) + .ToArr(); + + var input = new SimInput(parameters, isDefault: config.Input?.IsDefault == true); + + var values = config.Output?.Values?.IsNullOrEmpty() == true + ? default + : config.Output.Values + .Select(v => new SimValue(v.Name, v.Elements?.IsNullOrEmpty() == true + ? default + : v.Elements + .Select(e => new SimElement(e.Name, e.IsIndependentVariable, e.Unit, e.Description)) + .ToArr() + )) + .ToArr(); + + var output = new SimOutput(values); + + return new SimConfig( + config.Title, + config.Description, + config.ImportedOn, + code, + input, + output + ); + } + } +} diff --git a/RVis.Model/Sim/SimCode.cs b/RVis.Model/Sim/SimCode.cs new file mode 100644 index 0000000..f7ceebe --- /dev/null +++ b/RVis.Model/Sim/SimCode.cs @@ -0,0 +1,56 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimCode : IEquatable + { + public SimCode(string file, string exec, string formal) + { + _file = file; + _exec = exec; + _formal = formal; + } + + [ProtoIgnore] + public string File => _file; + + [ProtoIgnore] + public string Exec => _exec; + + [ProtoIgnore] + public string Formal => _formal; + + [ProtoMember(1)] + private readonly string _file; + + [ProtoMember(2)] + private readonly string _exec; + + [ProtoMember(3)] + private readonly string _formal; + + public bool Equals(SimCode rhs) => + _file == rhs._file && _exec == rhs._exec && _formal == rhs._formal; + + public override int GetHashCode() + { + var hashCode = -1933996224; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_file); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_exec); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_formal); + return hashCode; + } + + public override bool Equals(object obj) => + obj is SimCode code ? Equals(code) : false; + + public static bool operator ==(SimCode code1, SimCode code2) => + code1.Equals(code2); + + public static bool operator !=(SimCode code1, SimCode code2) => + !(code1 == code2); + } +} diff --git a/RVis.Model/Sim/SimConfig.cs b/RVis.Model/Sim/SimConfig.cs new file mode 100644 index 0000000..447fc38 --- /dev/null +++ b/RVis.Model/Sim/SimConfig.cs @@ -0,0 +1,95 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimConfig : IEquatable + { + public SimConfig(string title, string description, DateTime importedOn, SimCode code, SimInput input, SimOutput output) + { + _title = title; + _description = description; + _importedOn = importedOn; + _simCode = code; + _simInput = input; + _simOutput = output; + } + + [ProtoIgnore] + public string Title => _title; + + [ProtoIgnore] + public string Description => _description; + + [ProtoIgnore] + public DateTime ImportedOn => _importedOn; + + [ProtoIgnore] + public SimCode SimCode => _simCode; + + [ProtoIgnore] + public SimInput SimInput => _simInput; + + [ProtoIgnore] + public SimOutput SimOutput => _simOutput; + + public SimConfig With(SimInput SimInput = default) => + new SimConfig( + Title, + Description, + ImportedOn, + SimCode, + SimInput == default ? this.SimInput : SimInput, + SimOutput + ); + + public bool Equals(SimConfig rhs) => + _title == rhs._title && + _description == rhs._description && + _importedOn == rhs._importedOn && + _simCode == rhs._simCode && + _simInput == rhs._simInput && + _simOutput == rhs._simOutput; + + public override int GetHashCode() + { + var hashCode = -286846682; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_title); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_description); + hashCode = hashCode * -1521134295 + _importedOn.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_simCode); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_simInput); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_simOutput); + return hashCode; + } + + public override bool Equals(object obj) => + obj is SimConfig config ? Equals(config) : false; + + [ProtoMember(1)] + private readonly string _title; + + [ProtoMember(2)] + private readonly string _description; + + [ProtoMember(3)] + private readonly DateTime _importedOn; + + [ProtoMember(4)] + private readonly SimCode _simCode; + + [ProtoMember(5)] + private readonly SimInput _simInput; + + [ProtoMember(6)] + private readonly SimOutput _simOutput; + + public static bool operator ==(SimConfig config1, SimConfig config2) => + config1.Equals(config2); + + public static bool operator !=(SimConfig config1, SimConfig config2) => + !(config1 == config2); + } +} diff --git a/RVis.Model/Sim/SimElement.cs b/RVis.Model/Sim/SimElement.cs new file mode 100644 index 0000000..8ae527d --- /dev/null +++ b/RVis.Model/Sim/SimElement.cs @@ -0,0 +1,67 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimElement : IEquatable + { + public SimElement(string name, bool isIndependentVariable, string unit, string description) + { + _name = name; + _isIndependentVariable = isIndependentVariable; + _unit = unit; + _description = description; + } + + [ProtoIgnore] + public string Name => _name; + + [ProtoIgnore] + public bool IsIndependentVariable => _isIndependentVariable; + + [ProtoIgnore] + public string Unit => _unit; + + [ProtoIgnore] + public string Description => _description; + + [ProtoMember(1)] + private readonly string _name; + + [ProtoMember(2)] + private readonly bool _isIndependentVariable; + + [ProtoMember(3)] + private readonly string _unit; + + [ProtoMember(4)] + private readonly string _description; + + public bool Equals(SimElement rhs) => + _name == rhs._name && + _isIndependentVariable == rhs._isIndependentVariable && + _unit == rhs._unit && + _description == rhs._description; + + public override int GetHashCode() + { + var hashCode = 2131522645; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_name); + hashCode = hashCode * -1521134295 + _isIndependentVariable.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_unit); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_description); + return hashCode; + } + + public override bool Equals(object obj) => + obj is SimElement element ? Equals(element) : false; + + public static bool operator ==(SimElement element1, SimElement element2) => + element1.Equals(element2); + + public static bool operator !=(SimElement element1, SimElement element2) => + !(element1 == element2); + } +} diff --git a/RVis.Model/Sim/SimInput.cs b/RVis.Model/Sim/SimInput.cs new file mode 100644 index 0000000..811728f --- /dev/null +++ b/RVis.Model/Sim/SimInput.cs @@ -0,0 +1,94 @@ +using LanguageExt; +using ProtoBuf; +using RVis.Base.Extensions; +using RVis.Model.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimInput : IEquatable + { + public SimInput(Arr parameters, bool isDefault) + { + _parameters = parameters; + _parameterArray = default; + _isDefault = isDefault; + _hash = default; + } + + [ProtoIgnore] + public Arr SimParameters => _parameters; + + [ProtoIgnore] + public bool IsDefault => _isDefault; + + [ProtoIgnore] + public string Hash => _hash.IsntAString() ? (_hash = GenerateHash()) : _hash; + + public SimInput With(Arr SimParameters = default) + { + if (SimParameters == default) SimParameters = this.SimParameters; + + var withEdited = this.SimParameters.Map( + p => SimParameters.FindParameter(p).Match(e => e, () => p) + ); + + return new SimInput(withEdited.ToArr(), isDefault: false); + } + + public bool Equals(SimInput rhs) => + _parameters == rhs._parameters && _isDefault == rhs._isDefault; + + public override bool Equals(object obj) => + obj is SimInput rhs ? Equals(rhs) : false; + + public override int GetHashCode() + { + var hashCode = -537398035; + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(_parameters); + hashCode = hashCode * -1521134295 + _isDefault.GetHashCode(); + return hashCode; + } + + public static bool operator ==(SimInput lhs, SimInput rhs) => + lhs.Equals(rhs); + + public static bool operator !=(SimInput lhs, SimInput rhs) => + !(lhs == rhs); + + private string GenerateHash() + { + var sb = new StringBuilder(); + SimParameters.Iter(p => sb.Append(p.Name + p.Value)); + return sb.ToString().ToMD5Hash(); + } + + [ProtoBeforeSerialization] + private void OnSerializing() + { + _parameterArray = SimParameters.IsEmpty ? default : SimParameters.ToArray(); + } + + [ProtoAfterDeserialization] + private void OnDeserialized() + { + _parameters = _parameterArray == default ? default : _parameterArray.ToArr(); + } + + [ProtoIgnore] + private Arr _parameters; + + [ProtoMember(1)] + private SimParameter[] _parameterArray; + + [ProtoMember(2)] + private bool _isDefault; + + [ProtoIgnore] + private string _hash; + } +} diff --git a/RVis.Model/Sim/SimLibrary.cs b/RVis.Model/Sim/SimLibrary.cs new file mode 100644 index 0000000..9b4cb55 --- /dev/null +++ b/RVis.Model/Sim/SimLibrary.cs @@ -0,0 +1,134 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using System.IO; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Logger; +using static System.Globalization.CultureInfo; + +namespace RVis.Model +{ + public class SimLibrary + { + public SimLibrary() + { + + } + + public string Location { get; private set; } + + public Arr Simulations { get; private set; } = Arr.Empty; + + public (int Successes, int Failures) LoadFrom(string location) + { + RequireDirectory(location); + + var (simulations, failures) = Load(location); + + Location = location; + Simulations = simulations; + + OnLoaded(); + + return (simulations.Count, failures); + } + private void OnLoaded() => + Loaded?.Invoke(this, EventArgs.Empty); + public event EventHandler Loaded; + + private void OnDeleted(Simulation simulation) => + Deleted?.Invoke(this, new SimulationDeletedEventArgs(simulation)); + public event EventHandler Deleted; + + public (int Successes, int Failures) Refresh() + { + RequireDirectory(Location); + + var (simulations, failures) = Load(Location); + + Simulations = simulations; + + OnLoaded(); + + return (simulations.Count, failures); + } + + public string Import(string location) + { + RequireDirectory(location); + + var diSimulation = new DirectoryInfo(location); + var rFiles = diSimulation.GetFiles("*.R"); + RequireEqual(rFiles.Length, 1); + + var fiR = rFiles[0]; + var libraryDirectoryName = GetImportDirectoryName(fiR.Name); + var pathToSimulation = Path.Combine(Location, libraryDirectoryName); + + Directory.Move(location, pathToSimulation); + + return libraryDirectoryName; + } + + public void Delete(Simulation simulation) + { + var containingDirectoryName = DateTime.UtcNow.ToString("o", InvariantCulture).ToValidFileName(); + var pathToContainingDirectory = Path.Combine(Path.GetTempPath(), containingDirectoryName); + Directory.Move(simulation.PathToSimulation, pathToContainingDirectory); + try + { + Directory.Delete(pathToContainingDirectory, true); + Directory.Delete(simulation.PathToSimulation); + } + catch (Exception) + { + // the sim has gone from the lib so swallow + } + Simulations = Simulations.Remove(simulation); + OnDeleted(simulation); + } + + private static (Arr Simulations, int Failures) Load(string location) + { + var directory = new DirectoryInfo(location); + + var factory = fun( + (DirectoryInfo di) => Try(() => Simulation.LoadFrom(di.FullName)) + ); + + var attempts = directory + .GetDirectories() + .ToSeq() + .Filter(di => !di.Name.StartsWith(".", StringComparison.InvariantCulture)) + .Map(factory); + + var (exceptions, simulations) = partition(attempts); + + exceptions.Iter(Log.Error); + + return (simulations.ToArr(), exceptions.Length()); + } + + private string GetImportDirectoryName(string codeFileName) + { + RequireDirectory(Location); + + var directoryName = Path.GetFileNameWithoutExtension(codeFileName); + var pathToImport = Path.Combine(Location, directoryName); + if (!Directory.Exists(pathToImport)) return directoryName; + + var counter = 0; + string mangled; + do + { + ++counter; + mangled = $"{directoryName} ({counter:000})"; + pathToImport = Path.Combine(Location, mangled); + } + while (Directory.Exists(pathToImport)); + + return mangled; + } + } +} diff --git a/RVis.Model/Sim/SimOutput.cs b/RVis.Model/Sim/SimOutput.cs new file mode 100644 index 0000000..576a429 --- /dev/null +++ b/RVis.Model/Sim/SimOutput.cs @@ -0,0 +1,83 @@ +using LanguageExt; +using ProtoBuf; +using RVis.Base.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimOutput : IEquatable + { + public SimOutput(Arr values) + { + _values = values; + _valueArray = default; + _independentVariable = default; + _dependentVariables = default; + } + + [ProtoIgnore] + public Arr SimValues => _values; + + [ProtoIgnore] + public SimElement IndependentVariable => _independentVariable.Name.IsntAString() + ? (_independentVariable = GetIndependentVariable()) + : _independentVariable; + + [ProtoIgnore] + public Arr DependentVariables => _dependentVariables.IsEmpty + ? (_dependentVariables = GetDependentVariables()) + : _dependentVariables; + + private SimElement GetIndependentVariable() => SimValues + .SelectMany(v => v.SimElements) + .Single(e => e.IsIndependentVariable); + + private Arr GetDependentVariables() => + SimValues + .Bind(v => v.SimElements) + .Filter(e => !e.IsIndependentVariable) + .ToArr(); + + public bool Equals(SimOutput rhs) => + _values == rhs._values; + + public override int GetHashCode() => + -339605548 + EqualityComparer>.Default.GetHashCode(_values); + + public override bool Equals(object obj) => + obj is SimOutput output ? Equals(output) : false; + + [ProtoBeforeSerialization] + private void OnSerializing() + { + _valueArray = SimValues.IsEmpty ? default : SimValues.ToArray(); + } + + [ProtoAfterDeserialization] + private void OnDeserialized() + { + _values = _valueArray == default ? default : _valueArray.ToArr(); + } + + [ProtoIgnore] + private Arr _values; + + [ProtoMember(1)] + private SimValue[] _valueArray; + + [ProtoIgnore] + private SimElement _independentVariable; + + [ProtoIgnore] + private Arr _dependentVariables; + + public static bool operator ==(SimOutput output1, SimOutput output2) => + output1.Equals(output2); + + public static bool operator !=(SimOutput output1, SimOutput output2) => + !(output1 == output2); + } +} diff --git a/RVis.Model/Sim/SimParameter.cs b/RVis.Model/Sim/SimParameter.cs new file mode 100644 index 0000000..a1dda2b --- /dev/null +++ b/RVis.Model/Sim/SimParameter.cs @@ -0,0 +1,103 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.Globalization; +using static System.Double; +using static System.Globalization.CultureInfo; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimParameter : IEquatable + { + public SimParameter(string name, string value, string unit, string description) + { + _name = name; + _value = value; + _unit = unit; + _description = description; + + _scalar = value != default && TryParse(value, NumberStyles.Float, InvariantCulture, out double d) + ? d + : NaN; + } + + [ProtoIgnore] + public string Name => _name; + + [ProtoIgnore] + public string Value => _value; + + [ProtoIgnore] + public double Scalar => _scalar; + + [ProtoIgnore] + public string Unit => _unit; + + [ProtoIgnore] + public string Description => _description; + + public SimParameter With(string Value = null) => + new SimParameter(this, Value ?? this.Value); + + public SimParameter With(double value) => + new SimParameter(this, value); + + public bool Equals(SimParameter rhs) => + _name == rhs._name && + _value == rhs._value && + ((IsNaN(_scalar) && IsNaN(rhs._scalar)) || _scalar == rhs._scalar) && + _unit == rhs._unit && + _description == rhs._description; + + public override bool Equals(object obj) => + obj is SimParameter parameter ? Equals(parameter) : false; + + public override string ToString() => + $"{Name} = {Value:G4}{Unit}"; + + public override int GetHashCode() + { + var hashCode = -1648669195; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); + hashCode = hashCode * -1521134295 + Scalar.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Unit); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Description); + return hashCode; + } + + private SimParameter(SimParameter toCopy, string value) + : this(toCopy.Name, value, toCopy.Unit, toCopy.Description) + { + } + + private SimParameter(SimParameter toCopy, double value) + : this(toCopy.Name, default, toCopy.Unit, toCopy.Description) + { + _value = value.ToString(InvariantCulture); + _scalar = value; + } + + public static bool operator ==(SimParameter left, SimParameter right) => + left.Equals(right); + + public static bool operator !=(SimParameter left, SimParameter right) => + !(left == right); + + [ProtoMember(1)] + private readonly string _name; + + [ProtoMember(2)] + private readonly string _value; + + [ProtoMember(3)] + private readonly double _scalar; + + [ProtoMember(4)] + private readonly string _unit; + + [ProtoMember(5)] + private readonly string _description; + } +} diff --git a/RVis.Model/Sim/SimValue.cs b/RVis.Model/Sim/SimValue.cs new file mode 100644 index 0000000..da43873 --- /dev/null +++ b/RVis.Model/Sim/SimValue.cs @@ -0,0 +1,70 @@ +using LanguageExt; +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RVis.Model +{ + [ProtoContract] + public struct SimValue : IEquatable + { + public SimValue(string name, Arr elements) + { + _name = name; + _simElements = elements; + _simElementArray = default; + } + + [ProtoIgnore] + public string Name => _name; + + [ProtoIgnore] + public Arr SimElements => _simElements; + + public bool Equals(SimValue rhs) => + _name == rhs._name && _simElements == rhs._simElements; + + public override int GetHashCode() + { + var hashCode = 450469409; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_name); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(_simElements); + return hashCode; + } + + public override bool Equals(object obj) => + obj is SimValue value ? Equals(value) : false; + + [ProtoBeforeSerialization] + private void OnSerializing() + { + _simElementArray = SimElements.IsEmpty ? default : SimElements.ToArray(); + } + + [ProtoAfterDeserialization] + private void OnDeserialized() + { + _simElements = _simElementArray == default ? default : _simElementArray.ToArr(); + } + + [ProtoMember(1)] + private readonly string _name; + + [ProtoIgnore] + private Arr _simElements; + + [ProtoMember(2)] + private SimElement[] _simElementArray; + + public static bool operator ==(SimValue value1, SimValue value2) + { + return value1.Equals(value2); + } + + public static bool operator !=(SimValue value1, SimValue value2) + { + return !(value1 == value2); + } + } +} diff --git a/RVis.Model/Sim/Simulation.cs b/RVis.Model/Sim/Simulation.cs new file mode 100644 index 0000000..3e9a298 --- /dev/null +++ b/RVis.Model/Sim/Simulation.cs @@ -0,0 +1,139 @@ +using LanguageExt; +using Nett; +using ProtoBuf; +using RVis.Base.Extensions; +using RVis.Model.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using static RVis.Base.Check; +using static RVis.Model.Constant; +using static RVis.Model.Sim; + +namespace RVis.Model +{ + [ProtoContract] + public struct Simulation : IEquatable + { + public static Simulation LoadFrom(string pathToSimulation) + { + var pathToPrivate = Path.Combine(pathToSimulation, PRIVATE_SUBDIRECTORY); + RequireDirectory(pathToPrivate); + + var pathToConfig = Path.Combine(pathToPrivate, CONFIG_FILE_NAME); + RequireFile(pathToConfig); + + TSimConfig fromToml; + try + { + fromToml = Toml.ReadFile(pathToConfig); + } + catch (Exception ex) + { + throw new ArgumentException($"Failed to load config file", nameof(pathToSimulation), ex); + } + + var config = FromToml(fromToml); + + var simulation = new Simulation(pathToSimulation, config); + + return simulation; + } + + [ProtoIgnore] + public string PathToSimulation => _pathToSimulation; + + [ProtoIgnore] + public SimConfig SimConfig => _simConfig; + + [ProtoIgnore] + public string PathToCodeFile => Path.Combine(PathToSimulation, SimConfig.SimCode.File); + + public string PopulateTemplate(Arr parameters) + { + RequireTrue(this.IsTmplType()); + RequireFile(PathToCodeFile); + var template = File.ReadAllText(PathToCodeFile); + + var expectedParameterNames = SimConfig.SimInput.SimParameters.Map(p => p.Name); + var fullyPopulated = expectedParameterNames.ForAll( + n => parameters.ContainsParameter(n) + ); + RequireTrue(fullyPopulated); + + var pathToPopulated = Path.GetTempFileName(); + File.Move(pathToPopulated, pathToPopulated + ".R"); + pathToPopulated += ".R"; + + var populated = SubstitutePlaceholders(template, parameters); + + File.WriteAllText(pathToPopulated, populated); + + return pathToPopulated; + } + + private static string SubstitutePlaceholders(string template, Arr parameters) + { + var nSubstitutions = parameters.Count; + + var parts = template.Split(new string[] { "${" }, StringSplitOptions.None); + + RequireEqual(parts.Length, nSubstitutions + 1, "Incompatible parameter set and template: non-equal substitutions"); + + if (0 == nSubstitutions) return template; + + var sb = new StringBuilder(parts[0]); + + for (var i = 1; i < parts.Length; ++i) + { + var part = parts[i]; + var posRBrace = part.IndexOf('}'); + + RequireTrue(posRBrace.IsFound(), "No closing } in section: " + part); + + var name = part.Substring(0, posRBrace).Trim(); + var parameter = parameters + .FindParameter(name) + .AssertSome($"Template specifies unknown parameter: {name}"); + + sb.Append(parameter.GetRValue()); + sb.Append(part.Substring(posRBrace + 1)); + } + + return sb.ToString(); + } + + public bool Equals(Simulation rhs) => + _pathToSimulation == rhs._pathToSimulation && _simConfig == rhs._simConfig; + + public override bool Equals(object obj) => + obj is Simulation simulation ? Equals(simulation) : false; + + public override int GetHashCode() + { + var hashCode = 1016786274; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_pathToSimulation); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_simConfig); + return hashCode; + } + + private Simulation(string pathToSimulation, SimConfig simConfig) + { + _pathToSimulation = pathToSimulation; + _simConfig = simConfig; + } + + [ProtoMember(1)] + private readonly string _pathToSimulation; + + [ProtoMember(2)] + private readonly SimConfig _simConfig; + + public static bool operator ==(Simulation left, Simulation right) => + left.Equals(right); + + public static bool operator !=(Simulation left, Simulation right) => + !(left == right); + } +} diff --git a/RVis.Model/Sim/TOML/TSimCode.cs b/RVis.Model/Sim/TOML/TSimCode.cs new file mode 100644 index 0000000..6c3e3ba --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimCode.cs @@ -0,0 +1,11 @@ +namespace RVis.Model +{ + public class TSimCode + { + public string File { get; set; } + + public string Exec { get; set; } + + public string Formal { get; set; } + } +} diff --git a/RVis.Model/Sim/TOML/TSimConfig.cs b/RVis.Model/Sim/TOML/TSimConfig.cs new file mode 100644 index 0000000..25e471d --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimConfig.cs @@ -0,0 +1,19 @@ +using System; + +namespace RVis.Model +{ + public class TSimConfig + { + public string Title { get; set; } + + public string Description { get; set; } + + public DateTime ImportedOn { get; set; } + + public TSimCode Code { get; set; } + + public TSimInput Input { get; set; } + + public TSimOutput Output { get; set; } + } +} diff --git a/RVis.Model/Sim/TOML/TSimElement.cs b/RVis.Model/Sim/TOML/TSimElement.cs new file mode 100644 index 0000000..e864b9e --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimElement.cs @@ -0,0 +1,13 @@ +namespace RVis.Model +{ + public class TSimElement + { + public string Name { get; set; } + + public bool IsIndependentVariable { get; set; } + + public string Unit { get; set; } + + public string Description { get; set; } + } +} diff --git a/RVis.Model/Sim/TOML/TSimInput.cs b/RVis.Model/Sim/TOML/TSimInput.cs new file mode 100644 index 0000000..1a65d19 --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimInput.cs @@ -0,0 +1,8 @@ +namespace RVis.Model +{ + public sealed class TSimInput + { + public TSimParameter[] Parameters { get; set; } + public bool IsDefault { get; set; } + } +} diff --git a/RVis.Model/Sim/TOML/TSimOutput.cs b/RVis.Model/Sim/TOML/TSimOutput.cs new file mode 100644 index 0000000..47e4860 --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimOutput.cs @@ -0,0 +1,7 @@ +namespace RVis.Model +{ + public class TSimOutput + { + public TSimValue[] Values { get; set; } + } +} diff --git a/RVis.Model/Sim/TOML/TSimParameter.cs b/RVis.Model/Sim/TOML/TSimParameter.cs new file mode 100644 index 0000000..3362d7f --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimParameter.cs @@ -0,0 +1,13 @@ +namespace RVis.Model +{ + public class TSimParameter + { + public string Name { get; set; } + + public string Value { get; set; } + + public string Unit { get; set; } + + public string Description { get; set; } + } +} diff --git a/RVis.Model/Sim/TOML/TSimValue.cs b/RVis.Model/Sim/TOML/TSimValue.cs new file mode 100644 index 0000000..c9728f9 --- /dev/null +++ b/RVis.Model/Sim/TOML/TSimValue.cs @@ -0,0 +1,9 @@ +namespace RVis.Model +{ + public class TSimValue + { + public string Name { get; set; } + + public TSimElement[] Elements { get; set; } + } +} diff --git a/RVis.Model/SimData/Enum.cs b/RVis.Model/SimData/Enum.cs new file mode 100644 index 0000000..58546d0 --- /dev/null +++ b/RVis.Model/SimData/Enum.cs @@ -0,0 +1,14 @@ +namespace RVis.Model +{ + public enum SimDataEvent + { + ServiceReset + } + + public enum OutputOrigin + { + None, + Generation, + Storage + } +} diff --git a/RVis.Model/SimData/Impl/SimData.Dispose.cs b/RVis.Model/SimData/Impl/SimData.Dispose.cs new file mode 100644 index 0000000..30ddb59 --- /dev/null +++ b/RVis.Model/SimData/Impl/SimData.Dispose.cs @@ -0,0 +1,30 @@ +using System; + +namespace RVis.Model +{ + public sealed partial class SimData + { + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _executionIntervals.Values.Iter(v => v.Save()); + if (_outputRequestsSubject is IDisposable outputRequestsSubject) outputRequestsSubject.Dispose(); + _ctsDataService?.Cancel(); + _ctsDataService?.Dispose(); + _mreDataService.Dispose(); + _outputRequests.Clear(); + _outputs.Clear(); + } + + _disposed = true; + } + } + + private bool _disposed = false; + } +} diff --git a/RVis.Model/SimData/Impl/SimData.ExecInt.cs b/RVis.Model/SimData/Impl/SimData.ExecInt.cs new file mode 100644 index 0000000..4e37c54 --- /dev/null +++ b/RVis.Model/SimData/Impl/SimData.ExecInt.cs @@ -0,0 +1,112 @@ +using LanguageExt; +using Nett; +using RVis.Model.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using static MathNet.Numerics.Statistics.SortedArrayStatistics; +using static RVis.Base.Check; +using static RVis.Model.Logger; +using static System.Convert; + +namespace RVis.Model +{ + public sealed partial class SimData + { + internal sealed class SimExecutionInterval + { + internal SimExecutionInterval(Simulation simulation) + { + RequireNotNull(simulation); + + Simulation = simulation; + } + + internal Simulation Simulation { get; } + + internal Option<(int ms, int n)> GetExecutionInterval() + { + UpdateExecutionInterval(); + return _executionInterval; + } + + internal void Load() + { + var pathToExecutionInterval = Simulation.GetPrivatePath( + new[] { nameof(SimData) }, + EXECUTION_INTERVAL_FILE_NAME + ); + + if (File.Exists(pathToExecutionInterval)) + { + try + { + var tomlTable = Toml.ReadFile(pathToExecutionInterval); + int ms; int n; + ms = tomlTable.ContainsKey(nameof(ms)) ? tomlTable[nameof(ms)].Get() : default; + if (ms != default) + { + n = tomlTable.ContainsKey(nameof(n)) ? tomlTable[nameof(n)].Get() : default; + _executionInterval = (ms, n); + } + } + catch (Exception ex) + { + Log.Error(ex, $"{nameof(SimExecutionInterval)}.{nameof(Load)}"); + } + } + } + + internal void Save() + { + UpdateExecutionInterval(); + + _executionInterval.IfSome(ei => + { + if (ei != default) + { + var (ms, n) = ei; + var tomlTable = Toml.Create(); + tomlTable.Add(nameof(ms), ms); + tomlTable.Add(nameof(n), n); + var pathToExecutionInterval = Simulation.GetPrivatePath(new[] { nameof(SimData) }, EXECUTION_INTERVAL_FILE_NAME); + try + { + Toml.WriteFile(tomlTable, pathToExecutionInterval); + } + catch (Exception ex) + { + Log.Error(ex, $"{nameof(SimExecutionInterval)}.{nameof(Save)}"); + } + } + }); + } + + internal void AddInterval(long ms) => _executionIntervals.Add(ms); + + private void UpdateExecutionInterval() + { + var (_, n) = _executionInterval.IfNone((default, 0)); + + if (n < _executionIntervals.Count || _executionIntervals.Count > 30) + { + _executionIntervals.Sort(); + + var median = Median(_executionIntervals.Select(ToDouble).ToArray()); + + RequireTrue(median < int.MaxValue); + var ms = ToInt32(median); + + _executionInterval = (ms, _executionIntervals.Count); + } + } + + private const string EXECUTION_INTERVAL_FILE_NAME = "execution-interval.toml"; + + private Option<(int ms, int n)> _executionInterval; + private readonly List _executionIntervals = new List(); + } + } +} diff --git a/RVis.Model/SimData/Impl/SimData.OutputRequest.cs b/RVis.Model/SimData/Impl/SimData.OutputRequest.cs new file mode 100644 index 0000000..8e5c0a5 --- /dev/null +++ b/RVis.Model/SimData/Impl/SimData.OutputRequest.cs @@ -0,0 +1,221 @@ +using LanguageExt; +using RVis.Base.Extensions; +using RVis.Data; +using RVis.Model.Extensions; +using System; +using System.Diagnostics; +using System.Linq; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using static LanguageExt.Prelude; +using static RVis.Model.Logger; +using static System.Double; +using static System.String; + +namespace RVis.Model +{ + public sealed partial class SimData + { + private static Arr EvaluateNonScalars(SimInput seriesInput, SimInput defaultInput, IRVisClient client) + { + var nonScalarParameters = seriesInput.SimParameters.Filter(p => + { + var parameter = defaultInput.SimParameters.GetParameter(p.Name); + var isNonScalarEdit = p.Value != parameter.Value && IsNaN(p.Scalar); + return isNonScalarEdit; + }); + + if (nonScalarParameters.IsEmpty) return Array(seriesInput); + + var evaluations = nonScalarParameters.Map( + p => new + { + Parameter = p, + Evaluation = client.EvaluateNumData(p.Value) + }); + + var invalid = evaluations.Filter(a => a.Evaluation.Length != 1); + if (!invalid.IsEmpty) + throw new InvalidOperationException( + "Parameter values failed to produce single-column output: " + + Join(", ", invalid.Map(e => e.Parameter.Name)) + ); + + var singleColumns = evaluations + .Map(e => new { e.Parameter, Sequence = e.Evaluation[0].Data }); + var nParameters = singleColumns.Count; + var maxSequenceLength = singleColumns.Max(a => a.Sequence.Count); + var serieInputs = Range(0, maxSequenceLength).Map(i => + { + var parameters = singleColumns.Map( + a => a.Parameter.With(a.Sequence[i % a.Sequence.Count]) + ); + return seriesInput.With(parameters); + }); + + return serieInputs.ToArr(); + } + + private enum ProcessingOutcome + { + AcquiringData, + NoServerAvailable, + AlreadyAcquired + } + + private OutputRequest FulfilRequest(Simulation simulation, SimInput serieInput) + { + Log.Debug($"{nameof(SimData)} retrieving output"); + + var serie = simulation.LoadData(serieInput); + + _outputs.TryAdd( + (serieInput.Hash, simulation), + SimDataOutput.Create(simulation, serieInput, serie, OutputOrigin.Storage, false) + ); + + return OutputRequest.Create(serieInput, Array(serieInput)); + } + + private OutputRequest FulfilRequest(Simulation simulation, SimInput seriesInput, IRVisClient client, bool persist) + { + var serieInputs = EvaluateNonScalars( + seriesInput, + simulation.SimConfig.SimInput, + client + ); + + Log.Debug($"Evaluating non-scalar parameter values produced n={serieInputs.Count} series"); + + var useExec = simulation.SimConfig.SimCode.Exec.IsAString(); + + (SimInput SerieInput, NumDataTable Serie, bool Persist, OutputOrigin OutputOrigin) AcquireSerieData(SimInput serieInput) + { + if (simulation.HasData(serieInput)) + { + Log.Debug($"{nameof(SimData)} retrieving data"); + var retrieved = simulation.LoadData(serieInput); + return (serieInput, retrieved, false, OutputOrigin.Storage); + } + + Log.Debug($"{nameof(SimData)} generating data"); + + var serieConfig = simulation.SimConfig.With(serieInput); + NumDataTable serie; + + var stopWatch = Stopwatch.StartNew(); + + if (useExec) + { + client.RunExec(simulation.PathToCodeFile, serieConfig); + serie = client.TabulateExecOutput(serieConfig); + } + else + { + var pathToCodeFile = simulation.PopulateTemplate(serieConfig.SimInput.SimParameters); + client.SourceFile(pathToCodeFile); + serie = client.TabulateTmplOutput(serieConfig); + } + + stopWatch.Stop(); + var msElapsed = 1000L * stopWatch.ElapsedTicks / Stopwatch.Frequency; + + lock (_syncLock) + { + if (!_executionIntervals.TryGetValue(simulation, out SimExecutionInterval executionInterval)) + { + executionInterval = new SimExecutionInterval(simulation); + executionInterval.Load(); + _executionIntervals.Add(simulation, executionInterval); + } + + executionInterval.AddInterval(msElapsed); + } + + return (serieInput, serie, persist, OutputOrigin.Generation); + } + + var outputs = serieInputs.Map(AcquireSerieData); + + var outputsRequiringPersistence = outputs + .Filter(t => + { + var didAdd = _outputs.TryAdd( + (t.SerieInput.Hash, simulation), + SimDataOutput.Create(simulation, t.SerieInput, t.Serie, t.OutputOrigin, t.Persist) + ); + + return didAdd && t.Persist; + }); + + if (!outputsRequiringPersistence.IsEmpty) _mreDataService.Set(); + + return OutputRequest.Create( + seriesInput, + outputs.Map(t => t.SerieInput) + ); + } + + private ProcessingOutcome Process( + SimDataItem simDataItem, + CancellationToken cancellationToken, + out Task outputRequestTask + ) + { + var simulation = simDataItem.Simulation; + var seriesInput = simDataItem.Item.SeriesInput; + + ProcessingOutcome processingOutcome; + + if (_outputs.TryGetValue((seriesInput.Hash, simulation), out SimDataOutput _)) + { + outputRequestTask = Task.FromResult(OutputRequest.Create(seriesInput, Array(seriesInput))); + processingOutcome = ProcessingOutcome.AlreadyAcquired; + } + else if (simulation.HasData(seriesInput)) + { + outputRequestTask = Task.Run( + () => FulfilRequest(simulation, seriesInput), + cancellationToken + ); + processingOutcome = ProcessingOutcome.AcquiringData; + } + else + { + Task SomeServerLicense(ServerLicense serverLicense) => + Task.Run(() => + { + using (serverLicense) + { + return FulfilRequest( + simulation, + seriesInput, + serverLicense.Client, + simDataItem.Item.Persist + ); + } + }, cancellationToken); + + Task NoServerLicense() => + Task.FromResult(default(OutputRequest)); + + _serverPool.SlotFree.WaitOne(50); + + var maybeServerLicense = _serverPool.RequestServer(); + + outputRequestTask = maybeServerLicense.Match( + SomeServerLicense, + NoServerLicense + ); + + processingOutcome = + maybeServerLicense.IsSome ? + ProcessingOutcome.AcquiringData : + ProcessingOutcome.NoServerAvailable; + } + + return processingOutcome; + } + } +} diff --git a/RVis.Model/SimData/Impl/SimData.ServiceLoop.cs b/RVis.Model/SimData/Impl/SimData.ServiceLoop.cs new file mode 100644 index 0000000..36c31be --- /dev/null +++ b/RVis.Model/SimData/Impl/SimData.ServiceLoop.cs @@ -0,0 +1,168 @@ +using LanguageExt; +using RVis.Model.Extensions; +using System; +using System.Linq; +using System.Reactive.Linq; +using System.ServiceModel; +using System.Threading; +using System.Threading.Tasks; +using static RVis.Model.Logger; + +namespace RVis.Model +{ + public sealed partial class SimData + { + private void PersistOutputsImpl(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var firstUnpersisted = _outputs + .Where(kvp => kvp.Value.Persist && kvp.Value.PersistedOn == default) + .Take(1) + .ToArray(); + + if (!firstUnpersisted.Any()) break; + + var unpersisted = firstUnpersisted.Single().Value; + + DateTime persistedOn; + + try + { + unpersisted.Simulation.SaveData(unpersisted.Serie, unpersisted.SerieInput); + persistedOn = DateTime.UtcNow; + } + catch (Exception ex) + { + Log.Error(ex); + persistedOn = DateTime.MaxValue; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var persisted = unpersisted.ToPersisted(persistedOn); + var inputHash = firstUnpersisted.Single().Key; + _outputs.TryUpdate(inputHash, persisted, unpersisted); + + Log.Debug($"{nameof(SimData)} persisted {inputHash} output on {persistedOn}"); + + if (_outputRequests.Count > 0) break; + } + } + + private int AcquireOutputsImpl(CancellationToken cancellationToken) + { + var snapshot = _outputRequests + .ToArray() + .OrderBy(kvp => kvp.Value.RequestedOn); + + var nAwaitingServerLicense = 0; + + foreach (var item in snapshot) + { + var simDataItem = item.Value; + + try + { + Log.Debug($"{nameof(SimData)} processing {simDataItem.Item.SeriesInput.Hash} output request..."); + var outcome = Process(simDataItem, cancellationToken, out Task outputRequestTask); + + cancellationToken.ThrowIfCancellationRequested(); + + if (outcome == ProcessingOutcome.AlreadyAcquired || outcome == ProcessingOutcome.AcquiringData) + { + _outputRequests.TryRemove(item.Key, out SimDataItem _); + outputRequestTask.ContinueWith(task => + { + OutputRequest outputRequest; + if (task.IsFaulted) + { + outputRequest = OutputRequest.Create( + simDataItem.Item.SeriesInput, + task.Exception.InnerException + ); + } + else + { + outputRequest = task.Result; + } + + var fulfilled = SimDataItem.Create( + outputRequest, + simDataItem.Simulation, + simDataItem.Requester, + simDataItem.RequestToken, + simDataItem.RequestedOn, + DateTime.UtcNow + ); + _outputRequestsSubject.OnNext(fulfilled); + Log.Debug($"{nameof(SimData)} fulfilled {simDataItem.Item.SeriesInput.Hash} output request on {fulfilled.FulfilledOn}"); + }, cancellationToken); + } + else if (outcome == ProcessingOutcome.NoServerAvailable) + { + ++nAwaitingServerLicense; + } + else + { + throw new InvalidOperationException($"Unhandled processing outcome: {outcome}"); + } + } + catch (Exception ex) + { + _outputRequests.TryRemove(item.Key, out SimDataItem _); + if (ex is FaultException) ex = new Exception("Output not acquired (service failure)", ex); + var fulfilled = SimDataItem.Create( + OutputRequest.Create(simDataItem.Item.SeriesInput, ex), + simDataItem.Simulation, + simDataItem.Requester, + simDataItem.RequestToken, + simDataItem.RequestedOn, + DateTime.UtcNow + ); + _outputRequestsSubject.OnNext(fulfilled); + } + } + + return nAwaitingServerLicense; + } + + private void ServeDataImpl(Object stateInfo) + { + var cancellationToken = (CancellationToken)stateInfo; + + while (!cancellationToken.IsCancellationRequested) + { + if (_outputRequests.Count == 0) + { + Log.Debug("Checking for items to persist"); + PersistOutputsImpl(cancellationToken); + } + + if (_outputRequests.Count == 0) + { + Log.Debug("Waiting for service activation"); + WaitHandle.WaitAny( + new[] + { + _mreDataService.WaitHandle, + cancellationToken.WaitHandle + }); + Log.Debug("Service activated"); + } + + if (cancellationToken.IsCancellationRequested) break; + + int nAwaitingServerLicense = AcquireOutputsImpl(cancellationToken); + + if (0 < nAwaitingServerLicense) + { + Log.Debug($"{nameof(SimData)} has {nAwaitingServerLicense} items needing a server. Waiting for a slot..."); + _serverPool.SlotFree.WaitOne(); + } + + _mreDataService.Reset(); + } + } + } +} diff --git a/RVis.Model/SimData/Impl/SimData.SimOutput.cs b/RVis.Model/SimData/Impl/SimData.SimOutput.cs new file mode 100644 index 0000000..2bf0c57 --- /dev/null +++ b/RVis.Model/SimData/Impl/SimData.SimOutput.cs @@ -0,0 +1,34 @@ +using RVis.Data; +using System; + +namespace RVis.Model +{ + public sealed partial class SimData + { + internal class SimDataOutput + { + internal static SimDataOutput Create(Simulation simulation, SimInput serieInput, NumDataTable serie, OutputOrigin outputOrigin, bool persist) => + new SimDataOutput(simulation, serieInput, serie, outputOrigin, persist, default); + + internal SimDataOutput ToPersisted(DateTime persistedOn) => + new SimDataOutput(Simulation, SerieInput, Serie, OutputOrigin, Persist, persistedOn); + + private SimDataOutput(Simulation simulation, SimInput serieInput, NumDataTable serie, OutputOrigin outputOrigin, bool persist, DateTime persistedOn) + { + Simulation = simulation; + SerieInput = serieInput; + Serie = serie; + OutputOrigin = outputOrigin; + Persist = persist; + PersistedOn = persistedOn; + } + + internal Simulation Simulation { get; } + internal SimInput SerieInput { get; } + internal NumDataTable Serie { get; } + internal OutputOrigin OutputOrigin { get; } + internal bool Persist { get; } + internal DateTime PersistedOn { get; } + } + } +} \ No newline at end of file diff --git a/RVis.Model/SimData/Interface.cs b/RVis.Model/SimData/Interface.cs new file mode 100644 index 0000000..6b3bdfd --- /dev/null +++ b/RVis.Model/SimData/Interface.cs @@ -0,0 +1,20 @@ +using LanguageExt; +using RVis.Data; +using System; + +namespace RVis.Model +{ + public interface ISimData + { + IObservable> OutputRequests { get; } + bool HasOutput(string serieInputHash, Simulation simulation); + bool HasOutput(SimInput serieInput, Simulation simulation); + Option GetOutput(string serieInputHash, Simulation simulation); + Option GetOutput(SimInput serieInput, Simulation simulation); + Option<(SimInput SerieInput, OutputOrigin OutputOrigin, bool Persist, DateTime PersistedOn)> GetOutputInfo(string serieInputHash, Simulation simulation); + Option<(SimInput SerieInput, OutputOrigin OutputOrigin, bool Persist, DateTime PersistedOn)> GetOutputInfo(SimInput serieInput, Simulation simulation); + bool RequestOutput(Simulation simulation, SimInput seriesInput, object requester, object requestToken, bool persist); + void Clear(bool includePendingRequests); + Option<(int ms, int n)> GetExecutionInterval(Simulation simulation); + } +} diff --git a/RVis.Model/SimData/OutputRequest.cs b/RVis.Model/SimData/OutputRequest.cs new file mode 100644 index 0000000..f83d206 --- /dev/null +++ b/RVis.Model/SimData/OutputRequest.cs @@ -0,0 +1,31 @@ +using LanguageExt; +using System; +using static LanguageExt.Prelude; + +namespace RVis.Model +{ + public class OutputRequest + { + public static OutputRequest Create(SimInput seriesInput, bool persist) => + new OutputRequest(seriesInput, Either>.Bottom, persist); + + public static OutputRequest Create(SimInput seriesInput, Exception exception) => + new OutputRequest(seriesInput, Left(exception), false); + + public static OutputRequest Create(SimInput seriesInput, Arr serieInputs) => + new OutputRequest(seriesInput, Right>(serieInputs), false); + + private OutputRequest(SimInput seriesInput, Either> serieInputs, bool persist) + { + SeriesInput = seriesInput; + SerieInputs = serieInputs; + Persist = persist; + } + + public SimInput SeriesInput { get; } + + public Either> SerieInputs { get; } + + public bool Persist { get; } + } +} diff --git a/RVis.Model/SimData/SimData.cs b/RVis.Model/SimData/SimData.cs new file mode 100644 index 0000000..16a0443 --- /dev/null +++ b/RVis.Model/SimData/SimData.cs @@ -0,0 +1,171 @@ +using LanguageExt; +using RVis.Data; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Model.Logger; + +namespace RVis.Model +{ + public sealed partial class SimData : ISimData, IDisposable + { + public SimData(IRVisServerPool serverPool) + { + _serverPool = serverPool; + } + + public IObservable> OutputRequests => + _outputRequestsSubject.AsObservable(); + private readonly ISubject> _outputRequestsSubject = + Subject.Synchronize(new Subject>()); + + public bool HasOutput(string serieInputHash, Simulation simulation) => + _outputs.ContainsKey((serieInputHash, simulation)); + + public bool HasOutput(SimInput serieInput, Simulation simulation) => + HasOutput(serieInput.Hash, simulation); + + public Option GetOutput(string serieInputHash, Simulation simulation) + { + var got = _outputs.TryGetValue((serieInputHash, simulation), out SimDataOutput output); + return got ? Some(output.Serie) : None; + } + + public Option GetOutput(SimInput serieInput, Simulation simulation) => + GetOutput(serieInput.Hash, simulation); + + public Option<(SimInput SerieInput, OutputOrigin OutputOrigin, bool Persist, DateTime PersistedOn)> GetOutputInfo(string serieInputHash, Simulation simulation) + { + var got = _outputs.TryGetValue((serieInputHash, simulation), out SimDataOutput output); + return got ? Some((output.SerieInput, output.OutputOrigin, output.Persist, output.PersistedOn)) : None; + } + + public Option<(SimInput SerieInput, OutputOrigin OutputOrigin, bool Persist, DateTime PersistedOn)> GetOutputInfo(SimInput serieInput, Simulation simulation) => + GetOutputInfo(serieInput.Hash, simulation); + + public bool RequestOutput(Simulation simulation, SimInput seriesInput, object requester, object requestToken, bool persist) + { + if (!IsDataServiceRunning) StartDataService(); + + var seriesInputHash = seriesInput.Hash; + var key = (seriesInputHash, simulation, requester, requestToken).GetHashCode(); + + var outputRequest = OutputRequest.Create(seriesInput, persist); + var simDataItem = SimDataItem.Create(outputRequest, simulation, requester, requestToken, DateTime.UtcNow); + + var didAdd = _outputRequests.TryAdd(key, simDataItem); + + if (!didAdd) return false; + + Log.Debug($"{nameof(SimData)} queued {seriesInputHash} output request on {simDataItem.RequestedOn}"); + _mreDataService.Set(); + + return true; + } + + public void Clear(bool includePendingRequests) + { + if (includePendingRequests) _outputRequests.Clear(); + _outputs.Clear(); + } + + public Option<(int ms, int n)> GetExecutionInterval(Simulation simulation) + { + lock (_syncLock) + { + if (!_executionIntervals.ContainsKey(simulation)) + { + var executionInterval = new SimExecutionInterval(simulation); + executionInterval.Load(); + _executionIntervals.Add(simulation, executionInterval); + } + + return _executionIntervals[simulation].GetExecutionInterval(); + } + } + + public void ResetService() + { + _outputRequests.Clear(); + + var ctsDataService = _ctsDataService; + _serviceThread = null; + _ctsDataService = null; + + ctsDataService?.Cancel(); + ctsDataService?.Dispose(); + + NotifyObservers(SimDataEvent.ServiceReset); + } + + private void NotifyObservers(SimDataEvent @event) + { + var timeStamp = DateTime.UtcNow; + + void Notify(ISubject> subject) => + subject.OnNext( + SimDataItem.Create(default, default, this, @event, timeStamp) + ); + + // output request observers + Notify(_outputRequestsSubject); + + // others...? + } + + private void StartDataService() + { + RequireFalse(IsDataServiceRunning); + + var thread = new Thread(new ParameterizedThreadStart(ServeData)) + { + IsBackground = true + }; + _serviceThread = thread; + _ctsDataService = new CancellationTokenSource(); + + thread.Start(_ctsDataService.Token); + } + + private bool IsDataServiceRunning => _serviceThread?.IsAlive == true; + + private void ServeData(object stateInfo) + { + Log.Debug($"{nameof(SimData)} entering service loop on MTID={Thread.CurrentThread.ManagedThreadId}"); + + try + { + ServeDataImpl(stateInfo); + } + catch (OperationCanceledException) + { + Log.Debug($"{nameof(SimData)} got {nameof(OperationCanceledException)}"); + } + catch (Exception ex) + { + _outputRequestsSubject.OnError(ex); + Log.Error(ex, $"{nameof(SimData)} service loop"); + } + + Log.Debug($"{nameof(SimData)} exiting service loop"); + } + + private readonly IRVisServerPool _serverPool; + private readonly ConcurrentDictionary> _outputRequests = + new ConcurrentDictionary>(); + private readonly ConcurrentDictionary<(string InputHash, Simulation Simulation), SimDataOutput> _outputs = + new ConcurrentDictionary<(string InputHash, Simulation Simulation), SimDataOutput>(); + + private Thread _serviceThread; + private CancellationTokenSource _ctsDataService; + private readonly ManualResetEventSlim _mreDataService = new ManualResetEventSlim(false); + + private readonly IDictionary _executionIntervals = new Dictionary(); + private readonly object _syncLock = new object(); + } +} diff --git a/RVis.Model/SimData/SimDataItem.cs b/RVis.Model/SimData/SimDataItem.cs new file mode 100644 index 0000000..a6bb708 --- /dev/null +++ b/RVis.Model/SimData/SimDataItem.cs @@ -0,0 +1,60 @@ +using System; + +namespace RVis.Model +{ + public class SimDataItem + { + public T Item { get; } + + public Simulation Simulation { get; } + + public object Requester { get; } + + public object RequestToken { get; } + + public DateTime RequestedOn { get; } + + public DateTime FulfilledOn { get; } + + internal SimDataItem( + T item, + Simulation simulation, + object requester, + object requestToken, + DateTime requestedOn, + DateTime fulfilledOn + ) + { + Item = item; + Simulation = simulation; + Requester = requester; + RequestToken = requestToken; + RequestedOn = requestedOn; + FulfilledOn = fulfilledOn; + } + } + + internal static class SimDataItem + { + internal static SimDataItem Create( + T item, + Simulation simulation, + object requester, + object requestToken, + DateTime requestedOn + ) + => + new SimDataItem(item, simulation, requester, requestToken, requestedOn, default); + + internal static SimDataItem Create( + T item, + Simulation simulation, + object requester, + object requestToken, + DateTime requestedOn, + DateTime fulfilledOn + ) + => + new SimDataItem(item, simulation, requester, requestToken, requestedOn, fulfilledOn); + } +} diff --git a/RVis.Model/SimDataLog/SimDataLogArchive.cs b/RVis.Model/SimDataLog/SimDataLogArchive.cs new file mode 100644 index 0000000..5dff86c --- /dev/null +++ b/RVis.Model/SimDataLog/SimDataLogArchive.cs @@ -0,0 +1,190 @@ +using RVis.Model.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using static System.Globalization.CultureInfo; + +namespace RVis.Model +{ + public class SimDataLogArchive + { + public SimDataLogArchive(Simulation simulation) + { + _simulation = simulation; + } + + public Task> GetTodayAsync() + { + if (null != _today) return Task.FromResult(_today); + + return Task.Run(() => + { + _today = LoadToday(_simulation); + return _today; + }); + } + + public Task> GetThisMonthAsync() + { + if (null != _thisMonth) return Task.FromResult(_thisMonth); + + return Task.Run(() => + { + _thisMonth = LoadThisMonth(_simulation); + return _thisMonth; + }); + } + + public Task> GetThisYearAsync() + { + if (null != _thisYear) return Task.FromResult(_thisYear); + + return Task.Run(() => + { + _thisYear = LoadThisYear(_simulation); + return _thisYear; + }); + } + + public Task> GetPreviousYearsAsync() + { + if (null != _previousYears) return Task.FromResult(_previousYears); + + return Task.Run(() => + { + _previousYears = LoadPreviousYears(_simulation); + return _previousYears; + }); + } + + private static SimDataLogEntry[] LoadToday(Simulation simulation) + { + var pathToSessionLogDirectory = simulation.GetPathToSessionLogDirectory(); + var directory = new DirectoryInfo(pathToSessionLogDirectory); + if (!directory.Exists) + { + return Array.Empty(); + } + var logFiles = directory.GetFiles($"*.{SimExt.LogFileExtension}"); + var sessionLogEntries = new List(); + foreach (var logFile in logFiles) + { + if (logFile.Name == SimExt.SessionLogFileName) continue; + + try + { + var lines = File.ReadAllLines(logFile.FullName); + var logEntries = lines.Select(l => SimDataLogEntry.Parse(l)).Somes(); + sessionLogEntries.AddRange(logEntries); + } + catch (Exception ex) + { + Logger.Log.Error(ex, $"Failed to read log file {logFile.FullName}"); + } + } + return sessionLogEntries.OrderBy(le => le.EnteredOn).ToArray(); + } + + private static SimDataLogEntry[] LoadThisMonth(Simulation simulation) + { + var pathToLogDirectory = simulation.GetPathToLogDirectory(); + if (!Directory.Exists(pathToLogDirectory)) + { + return Array.Empty(); + } + var now = DateTime.Now; + var directoryPrefix = $"{now.ToString("yyyy-MM", InvariantCulture)}-"; + var today = now.Day; + + var logEntries = new List(); + for (var i = 1; i < today; ++i) + { + var pathToLogFileDirectory = Path.Combine(pathToLogDirectory, $"{directoryPrefix}{i:00}"); + logEntries.AddRange(ReadAllLogFiles(pathToLogFileDirectory)); + } + + return logEntries.OrderBy(le => le.EnteredOn).ToArray(); + } + + private static SimDataLogEntry[] LoadThisYear(Simulation simulation) + { + var pathToLogDirectory = simulation.GetPathToLogDirectory(); + if (!Directory.Exists(pathToLogDirectory)) + { + return Array.Empty(); + } + var logDirectory = new DirectoryInfo(pathToLogDirectory); + var now = DateTime.Now; + var logFileDirectories = logDirectory.GetDirectories($"{now.ToString("yyyy", InvariantCulture)}*", SearchOption.TopDirectoryOnly); + + var thisMonthDirectoryPrefix = $"{now.ToString("yyyy-MM", InvariantCulture)}-"; + var logEntries = new List(); + + foreach (var logFileDirectory in logFileDirectories) + { + if (logFileDirectory.Name.StartsWith(thisMonthDirectoryPrefix, StringComparison.InvariantCulture)) continue; + + logEntries.AddRange(ReadAllLogFiles(logFileDirectory.FullName)); + } + + return logEntries.OrderBy(le => le.EnteredOn).ToArray(); + } + + private static SimDataLogEntry[] LoadPreviousYears(Simulation simulation) + { + var pathToLogDirectory = simulation.GetPathToLogDirectory(); + if (!Directory.Exists(pathToLogDirectory)) + { + return Array.Empty(); + } + var logDirectory = new DirectoryInfo(pathToLogDirectory); + var now = DateTime.Now; + var logFileDirectories = logDirectory.GetDirectories(); + + var thisYearDirectoryPrefix = $"{now.ToString("yyyy", InvariantCulture)}-"; + var logEntries = new List(); + + foreach (var logFileDirectory in logFileDirectories) + { + if (logFileDirectory.Name.StartsWith(thisYearDirectoryPrefix, StringComparison.InvariantCulture)) continue; + + logEntries.AddRange(ReadAllLogFiles(logFileDirectory.FullName)); + } + + return logEntries.OrderBy(le => le.EnteredOn).ToArray(); + } + + private static IEnumerable ReadAllLogFiles(string pathToLogFileDirectory) + { + var directory = new DirectoryInfo(pathToLogFileDirectory); + if (!directory.Exists) return Enumerable.Empty(); + + var logEntries = new List(); + + var logFiles = directory.GetFiles($"*.{SimExt.LogFileExtension}"); + foreach (var logFile in logFiles) + { + try + { + var lines = File.ReadAllLines(logFile.FullName); + logEntries.AddRange(lines.Select(l => SimDataLogEntry.Parse(l)).Somes()); + } + catch (Exception ex) + { + Logger.Log.Error(ex, $"Failed to read log file {logFile.FullName}"); + } + } + + return logEntries; + } + + private IReadOnlyCollection _today; + private IReadOnlyCollection _thisMonth; + private IReadOnlyCollection _thisYear; + private IReadOnlyCollection _previousYears; + + private readonly Simulation _simulation; + } +} diff --git a/RVis.Model/SimDataLog/SimDataLogEntry.cs b/RVis.Model/SimDataLog/SimDataLogEntry.cs new file mode 100644 index 0000000..6627667 --- /dev/null +++ b/RVis.Model/SimDataLog/SimDataLogEntry.cs @@ -0,0 +1,49 @@ +using LanguageExt; +using System; +using static LanguageExt.Prelude; + +namespace RVis.Model +{ + public class SimDataLogEntry + { + internal static Option Parse(string line) + { + var parts = line.Split('$'); + if (parts.Length != 6 || + !int.TryParse(parts[0], out int id) || + !DateTime.TryParse(parts[1], out DateTime enteredOn)) + { + Logger.Log.Warn($"Failed to parse log entry: {line}"); + return None; + } + return new SimDataLogEntry( + id, + enteredOn, + parts[2], + parts[3], + parts[4], + parts[5] + ); + } + + internal SimDataLogEntry(int id, DateTime enteredOn, string serieInputHash, string parameterAssignments, string requesterTypeName, string description) + { + ID = id; + EnteredOn = enteredOn; + SerieInputHash = serieInputHash; + ParameterAssignments = parameterAssignments; + RequesterTypeName = requesterTypeName; + Description = description; + } + + public int ID { get; } + public DateTime EnteredOn { get; } + public string SerieInputHash { get; } + public string ParameterAssignments { get; } + public string RequesterTypeName { get; } + public string Description { get; } + + public override string ToString() => + $"{ID:00000000}${EnteredOn:o}${SerieInputHash}${ParameterAssignments}${RequesterTypeName}${Description}"; + } +} diff --git a/RVis.Model/SimDataLog/SimDataSessionLog.cs b/RVis.Model/SimDataLog/SimDataSessionLog.cs new file mode 100644 index 0000000..0d1fc28 --- /dev/null +++ b/RVis.Model/SimDataLog/SimDataSessionLog.cs @@ -0,0 +1,230 @@ +using LanguageExt; +using RVis.Base.Extensions; +using RVis.Model.Extensions; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using static LanguageExt.Prelude; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public sealed class SimDataSessionLog : IDisposable + { + public SimDataSessionLog(SimData simData, IObservable secondInterval) + { + _simData = simData; + _secondInterval = secondInterval; + } + + public IObservable LogEntries => + _logEntriesSubject.AsObservable(); + private readonly ISubject _logEntriesSubject = + new Subject(); + + public Arr Log + { + get + { + lock (_logEntriesSyncLock) + { + return _logEntries.ToArr(); + } + } + } + + public void StartSession(Simulation simulation) + { + RequireTrue(_logEntries.Count == 0); + + _logIndex = 0; + + var pathToSessionLog = simulation.GetPathToSessionLog(); + + if (File.Exists(pathToSessionLog)) + { + var lines = File.ReadAllLines(pathToSessionLog); + var logEntries = lines.Select(l => SimDataLogEntry.Parse(l)).Somes(); + lock (_logEntriesSyncLock) + { + _logEntries.AddRange(logEntries); + _logIndex = logEntries.Max(le => le.ID) + 1; + } + } + + _secondIntervalSubscription = _secondInterval.Subscribe(ObserveSecondInterval); + _outputRequestSubscription = _simData.OutputRequests.Subscribe(ObserveOutputRequest); + } + + public void EndSession() + { + _secondIntervalSubscription?.Dispose(); + _secondIntervalSubscription = default; + _outputRequestSubscription?.Dispose(); + _outputRequestSubscription = default; + + if (HaveOngoingWriteLogEntriesTask) _writeLogEntriesTask.Wait(); + _writeLogEntriesTask = default; + if (_pendingToFile.Count > 0) WritePendingToFile(); + + lock (_logEntriesSyncLock) + { + _logEntries.Clear(); + } + } + + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_logEntriesSubject is IDisposable logEntriesSubject) logEntriesSubject.Dispose(); + + _secondIntervalSubscription?.Dispose(); + _outputRequestSubscription?.Dispose(); + + if (HaveOngoingWriteLogEntriesTask) _writeLogEntriesTask.Wait(); + + if (_pendingToFile.Count > 0) WritePendingToFile(); + } + + _disposed = true; + } + } + + private void ObserveOutputRequest(SimDataItem simDataItem) + { + if (simDataItem.IsResetEvent()) return; + + simDataItem.Item.SerieInputs.IfRight( + o => LogOutputRequest(simDataItem, o) + ); + } + + private void LogOutputRequest(SimDataItem simDataItem, Arr serieInputs) + { + var requesterTypeName = simDataItem.Requester.GetType().Name; + var description = simDataItem.GetDescription(); + var simulation = simDataItem.Simulation; + var defaultInput = simulation.SimConfig.SimInput; + + var logging = serieInputs + .Map(i => + { + var outputInfo = _simData.GetOutputInfo(i, simulation); + return outputInfo.Match( + oi => Some((Input: i, oi.SerieInput.Hash, oi.Persist)), + () => None + ); + }) + .Somes() + .Map(t => new + { + LogEntry = new SimDataLogEntry( + ++_logIndex, + DateTime.UtcNow, + t.Hash, + defaultInput.GetEdits(t.Input).ToAssignments(), + requesterTypeName, + description + ), + t.Persist + }) + .ToArr(); + + logging + .Filter(l => l.Persist) + .Iter(l => _pendingToFile.Enqueue((simulation, l.LogEntry))); + + var sessionLogIsRequester = requesterTypeName == ToString(); + + if (!sessionLogIsRequester) + { + var logEntries = logging.Map(l => l.LogEntry); + + lock (_logEntriesSyncLock) + { + _logEntries.AddRange(logEntries); + } + + logEntries.Iter(le => _logEntriesSubject.OnNext(le)); + } + } + + private void ObserveSecondInterval(long _) + { + PersistPendingLogEntries(); + } + + private void PersistPendingLogEntries() + { + if (HaveOngoingWriteLogEntriesTask) return; + + _writeLogEntriesTask = null; + + if (_pendingToFile.Count == 0) return; + + _writeLogEntriesTask = Task.Run(WritePendingToFile); + } + + private void WritePendingToFile() + { + try + { + if (!_pendingToFile.TryDequeue(out (Simulation Simulation, SimDataLogEntry LogEntry) pending)) return; + + var logEntries = new List + { + pending.LogEntry + }; + + var simulation = pending.Simulation; + var pathToSessionLog = simulation.GetPathToSessionLog(); + var pathToSessionLogDirectory = Path.GetDirectoryName(pathToSessionLog); + if (!Directory.Exists(pathToSessionLogDirectory)) Directory.CreateDirectory(pathToSessionLogDirectory); + + using (var file = new StreamWriter(pathToSessionLog, true)) + { + while (_pendingToFile.TryDequeue(out pending)) + { + RequireTrue(simulation == pending.Simulation); + logEntries.Add(pending.LogEntry); + } + foreach (var logEntry in logEntries) file.WriteLine(logEntry.ToString()); + } + } + catch (Exception ex) + { + Logger.Log.Error(ex, $"{nameof(SimDataSessionLog)}::{nameof(WritePendingToFile)}"); + } + } + + private bool HaveOngoingWriteLogEntriesTask => + null != _writeLogEntriesTask && + _writeLogEntriesTask.IsCompleted != true && + _writeLogEntriesTask.IsFaulted != true; + + private readonly SimData _simData; + private readonly IObservable _secondInterval; + + private IDisposable _secondIntervalSubscription; + private IDisposable _outputRequestSubscription; + + private int _logIndex; + private readonly List _logEntries = new List(); + private readonly object _logEntriesSyncLock = new object(); + private readonly ConcurrentQueue<(Simulation Simulation, SimDataLogEntry LogEntry)> _pendingToFile = + new ConcurrentQueue<(Simulation Simulation, SimDataLogEntry LogEntry)>(); + private Task _writeLogEntriesTask; + + private bool _disposed = false; + } +} diff --git a/RVis.Model/SimEvidence/DTO/EvidenceSourceDTO.cs b/RVis.Model/SimEvidence/DTO/EvidenceSourceDTO.cs new file mode 100644 index 0000000..63f6d10 --- /dev/null +++ b/RVis.Model/SimEvidence/DTO/EvidenceSourceDTO.cs @@ -0,0 +1,17 @@ +namespace RVis.Model +{ + internal class EvidenceSourceDTO + { + public int ID { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string[] Subjects { get; set; } + + public string RefName { get; set; } + + public string RefHash { get; set; } + } +} diff --git a/RVis.Model/SimEvidence/DTO/EvidenceSourcesDTO.cs b/RVis.Model/SimEvidence/DTO/EvidenceSourcesDTO.cs new file mode 100644 index 0000000..cae6537 --- /dev/null +++ b/RVis.Model/SimEvidence/DTO/EvidenceSourcesDTO.cs @@ -0,0 +1,43 @@ +using LanguageExt; +using System.Linq; +using static LanguageExt.Prelude; + +namespace RVis.Model +{ + internal class EvidenceSourcesDTO + { + internal static EvidenceSourcesDTO ToDTO(Arr evidenceSources) + { + return new EvidenceSourcesDTO + { + EvidenceSources = evidenceSources + .Map(es => new EvidenceSourceDTO + { + ID = es.ID, + Name = es.Name, + Description = es.Description, + Subjects = es.Subjects.ToArray(), + RefName = es.RefName, + RefHash = es.RefHash + }) + .ToArray() + }; + } + + internal static Arr FromDTO(EvidenceSourcesDTO evidenceSourcesDTO) + { + return evidenceSourcesDTO.EvidenceSources + .Select(dto => new SimEvidenceSource( + dto.ID, + dto.Name, + dto.Description, + toSet(dto.Subjects), + dto.RefName, + dto.RefHash + )) + .ToArr(); + } + + public EvidenceSourceDTO[] EvidenceSources { get; set; } + } +} diff --git a/RVis.Model/SimEvidence/Impl/SimObservationsSet.cs b/RVis.Model/SimEvidence/Impl/SimObservationsSet.cs new file mode 100644 index 0000000..a058a89 --- /dev/null +++ b/RVis.Model/SimEvidence/Impl/SimObservationsSet.cs @@ -0,0 +1,187 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System.IO; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Base.Check; +using static RVis.Base.Extensions.NumExt; +using static System.Environment; +using static System.IO.File; +using static System.IO.Path; +using static System.String; + +namespace RVis.Model +{ + public readonly partial struct SimObservationsSet + { + internal static SimObservationsSet LoadOrCreate(string pathToEvidenceDirectory, string subject) + { + var pathToCsvFile = Combine(pathToEvidenceDirectory, subject + ".csv"); + if (!File.Exists(pathToCsvFile)) + { + return new SimObservationsSet(subject, Arr.Empty); + } + + var lines = ReadAllLines(pathToCsvFile); + if (lines.IsEmpty()) + { + return new SimObservationsSet(subject, Arr.Empty); + } + + var id = NOT_FOUND; + var evidenceSourceID = NOT_FOUND; + string refName = default; + + var triples = lines + .Skip(1) // header + .Select(l => + { + var parts = l.Split(',').Select(p => p.Trim()).ToArray(); + + RequireTrue( + parts.Length > 1, + $"Invalid evidence CSV in {pathToCsvFile}: {l}" + ); + RequireFalse( + parts.Length == 2 && (id.IsntFound() || evidenceSourceID.IsntFound()), + $"Invalid evidence CSV in {pathToCsvFile}: {l}" + ); + RequireTrue( + double.TryParse(parts[0], out double x), + $"Invalid X in {pathToCsvFile}: {parts[0]}" + ); + RequireTrue( + double.TryParse(parts[1], out double y), + $"Invalid {subject} in {pathToCsvFile}: {parts[1]}" + ); + + if (parts.Length > 2) + { + RequireTrue( + parts.Length == 5, + $"Invalid evidence CSV in {pathToCsvFile}: {l}" + ); + RequireTrue( + int.TryParse(parts[2], out id), + $"Invalid ID in {pathToCsvFile}: {parts[2]}" + ); + RequireTrue( + int.TryParse(parts[3], out evidenceSourceID), + $"Invalid evidence source ID in {pathToCsvFile}: {parts[3]}" + ); + + refName = parts[4].Trim('"').Trim(); + RequireTrue(refName.IsAString()); + } + + return (X: x, Y: y, RefName: refName, EvidenceSourceID: evidenceSourceID, ID: id); + }); + + var observations = triples + .GroupBy(t => t.ID) + .Select(g => new SimObservations( + g.Key, + g.First().EvidenceSourceID, + subject, + g.First().RefName, + g.Select(t => t.X).ToArr(), + g.Select(t => t.Y).ToArr() + )) + .ToArr(); + + RequireTrue( + observations.IsEmpty || observations.AllUnique(o => o.ID), + "Found duplicate IDs" + ); + RequireTrue( + observations.IsEmpty || observations.AllUnique(o => (o.EvidenceSourceID, o.RefName)), + "Found duplicate RefNames" + ); + + return new SimObservationsSet(subject, observations); + } + + internal static void Save(SimObservationsSet observationsSet, string pathToEvidenceDirectory) + { + RequireDirectory(pathToEvidenceDirectory); + + var pathToCsvFile = Combine(pathToEvidenceDirectory, observationsSet.Subject + ".csv"); + if (File.Exists(pathToCsvFile)) File.Delete(pathToCsvFile); + + string ObservationToLine(SimObservations observations, int row) + { + var s = $"{observations.X[row]},{observations.Y[row]}"; + if (0 == row) + { + s = $"{s},{observations.ID},{observations.EvidenceSourceID},{observations.RefName.ToCsvQuoted()}"; + } + return s; + } + + var lines = observationsSet.Observations + .Map(o => Range(0, o.X.Count).Select(i => ObservationToLine(o,i)).ToArr()) + .Bind(ss => ss); + + var csv = + $"x,{observationsSet.Subject.Replace(',', '_')},oid,esid,ref" + NewLine + + Join(NewLine, lines); + + WriteAllText(pathToCsvFile, csv); + } + + internal static (SimObservationsSet ObservationsSet, SimObservations Observations) AddObservations( + SimObservationsSet observationsSet, + int evidenceSourceID, + string refName, + Arr x, + Arr y + ) + { + RequireFalse(observationsSet.Observations.Exists( + o => o.EvidenceSourceID == evidenceSourceID && o.RefName == refName) + ); + + var id = observationsSet.Observations.IsEmpty + ? 1 + : observationsSet.Observations.Max(o => o.ID) + 1; + + var observations = new SimObservations( + id, + evidenceSourceID, + observationsSet.Subject, + refName, + x, + y + ); + + observationsSet = new SimObservationsSet( + observationsSet.Subject, + observationsSet.Observations.Add(observations) + ); + + return (observationsSet, observations); + } + + internal static SimObservationsSet RemoveAllObservations( + SimObservationsSet observationsSet, + int evidenceSourceID + ) + { + var observations = observationsSet.Observations.Filter( + o => o.EvidenceSourceID != evidenceSourceID + ); + return new SimObservationsSet(observationsSet.Subject, observations); + } + + internal static SimObservationsSet RemoveObservations( + SimObservationsSet observationsSet, + SimObservations toRemove + ) + { + var observations = observationsSet.Observations.Filter( + o => o.ID != toRemove.ID + ); + return new SimObservationsSet(observationsSet.Subject, observations); + } + } +} diff --git a/RVis.Model/SimEvidence/Interface.cs b/RVis.Model/SimEvidence/Interface.cs new file mode 100644 index 0000000..a184a63 --- /dev/null +++ b/RVis.Model/SimEvidence/Interface.cs @@ -0,0 +1,33 @@ +using LanguageExt; +using RVis.Base; +using System; + +namespace RVis.Model +{ + public interface ISimEvidence + { + Arr EvidenceSources { get; } + SimEvidenceSource AddEvidenceSource( + string name, + string description, + Set subjects, + string reference, + string refHash + ); + void RemoveEvidenceSource(int id); + IObservable<(SimEvidenceSource EvidenceSource, ObservableQualifier Change)> EvidenceSourcesChanges { get; } + + Arr AddObservations( + int evidenceSourceID, + Arr<(string Subject, string RefName, Arr X, Arr Y)> observationsSets + ); + void RemoveObservations(int evidenceSourceID, string subject); + void RemoveObservations(SimObservations observations); + IObservable<(Arr Observations, ObservableQualifier Change)> ObservationsChanges { get; } + + Arr GetObservations(int evidenceSourceID); + SimObservationsSet GetObservationSet(string subject); + + Set Subjects { get; } + } +} diff --git a/RVis.Model/SimEvidence/SimEvidence.cs b/RVis.Model/SimEvidence/SimEvidence.cs new file mode 100644 index 0000000..b4eb587 --- /dev/null +++ b/RVis.Model/SimEvidence/SimEvidence.cs @@ -0,0 +1,321 @@ +using LanguageExt; +using RVis.Base; +using RVis.Base.Extensions; +using RVis.Model.Extensions; +using System; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using static LanguageExt.Prelude; +using static Nett.Toml; +using static RVis.Base.Check; +using static RVis.Model.Constant; +using static System.IO.Path; + +namespace RVis.Model +{ + public sealed class SimEvidence : ISimEvidence, IDisposable + { + public SimEvidence() + { + } + + public Arr EvidenceSources { get; private set; } + + public SimEvidenceSource AddEvidenceSource( + string name, + string description, + Set subjects, + string refName, + string refHash + ) + { + lock (_syncLock) + { + RequireFalse( + EvidenceSources.ContainsEvidenceSource(refHash), + $"Duplicate evidence source: {refHash}" + ); + RequireTrue(subjects.ForAll(IsSubject)); + + var id = EvidenceSources.IsEmpty ? 1 : 1 + EvidenceSources.Max(es => es.ID); + + var evidenceSource = new SimEvidenceSource(id, name, description, subjects, refName, refHash); + + var evidenceSources = EvidenceSources.Add(evidenceSource); + Save(evidenceSources, _pathToEvidenceDirectory); + EvidenceSources = evidenceSources; + + _evidenceSourcesChangesSubject.OnNext((evidenceSource, ObservableQualifier.Add)); + + return evidenceSource; + } + } + + public void RemoveEvidenceSource(int id) + { + lock (_syncLock) + { + var index = EvidenceSources.FindIndex(es => es.ID == id); + RequireTrue(index.IsFound(), $"Unknown evidence source: {id}"); + + var evidenceSource = EvidenceSources[index]; + + var observations = evidenceSource.Subjects + .Map(s => RemoveObservationsImpl(id, s)) + .Bind(a => a) + .ToArr(); + + _observationsChangesSubject.OnNext((observations, ObservableQualifier.Remove)); + + var evidenceSources = EvidenceSources.RemoveAt(index); + Save(evidenceSources, _pathToEvidenceDirectory); + EvidenceSources = evidenceSources; + + _evidenceSourcesChangesSubject.OnNext((evidenceSource, ObservableQualifier.Remove)); + } + } + + public IObservable<(SimEvidenceSource EvidenceSource, ObservableQualifier Change)> EvidenceSourcesChanges => + _evidenceSourcesChangesSubject.AsObservable(); + + public Arr AddObservations( + int evidenceSourceID, + Arr<(string Subject, string RefName, Arr X, Arr Y)> additions + ) + { + lock (_syncLock) + { + var esIndex = EvidenceSources.FindIndex(es => es.ID == evidenceSourceID); + RequireTrue(esIndex.IsFound(), $"Unknown evidence source: {evidenceSourceID}"); + + var evidenceSource = EvidenceSources[esIndex]; + + RequireTrue(additions + .Map(a => a.Subject) + .ForAll(s => IsSubject(s) && evidenceSource.Subjects.Contains(s)), + "Subjects mismatch in additions" + ); + + var edits = additions + .GroupBy(a => a.Subject) + .Select(g => + { + var observationsSet = FindOrLoadOrCreateObservationsSet(g.Key); + + var addedObservations = g + .Select(a => + { + var (addedTo, added) = SimObservationsSet.AddObservations( + observationsSet, + evidenceSourceID, + a.RefName, + a.X, + a.Y + ); + observationsSet = addedTo; + return added; + }) + .ToArr(); + + return (ObservationsSet: observationsSet, AddedObservations: addedObservations); + }) + .ToArr(); + + var observationsSets = _observationsSets; + + void SaveObservationsSet(SimObservationsSet observationsSet) + { + SimObservationsSet.Save(observationsSet, _pathToEvidenceDirectory); + var osIndex = observationsSets.FindIndex(os => os.Subject == observationsSet.Subject); + observationsSets = osIndex.IsFound() + ? observationsSets.SetItem(osIndex, observationsSet) + : observationsSets.Add(observationsSet); + } + + edits.Map(e => e.ObservationsSet).Iter(SaveObservationsSet); + + _observationsSets = observationsSets; + + var observations = edits.Bind(e => e.AddedObservations); + + _observationsChangesSubject.OnNext((observations, ObservableQualifier.Add)); + + return observations; + } + } + + public void RemoveObservations(int evidenceSourceID, string subject) + { + lock (_syncLock) + { + RequireTrue(IsSubject(subject)); + + var removed = RemoveObservationsImpl(evidenceSourceID, subject); + + _observationsChangesSubject.OnNext((removed, ObservableQualifier.Remove)); + } + } + + public void RemoveObservations(SimObservations observations) + { + lock (_syncLock) + { + var observationsSet = FindOrLoadOrCreateObservationsSet(observations.Subject); + observationsSet = SimObservationsSet.RemoveObservations(observationsSet, observations); + SimObservationsSet.Save(observationsSet, _pathToEvidenceDirectory); + var index = _observationsSets.FindIndex(os => os.Subject == observationsSet.Subject); + var observationsSets = index.IsFound() + ? _observationsSets.SetItem(index, observationsSet) + : _observationsSets.Add(observationsSet); + _observationsChangesSubject.OnNext((Array(observations), ObservableQualifier.Remove)); + } + } + + public IObservable<(Arr Observations, ObservableQualifier Change)> ObservationsChanges => + _observationsChangesSubject.AsObservable(); + + public Arr GetObservations(int evidenceSourceID) + { + lock (_syncLock) + { + var index = EvidenceSources.FindIndex(es => es.ID == evidenceSourceID); + RequireTrue(index.IsFound(), $"Unknown evidence source: {evidenceSourceID}"); + + var observationsSets = EvidenceSources[index].Subjects + .ToArr() + .Map(s => FindOrLoadOrCreateObservationsSet(s)); + + var missing = observationsSets.Filter(os => !_observationsSets.ContainsObservationsSet(os.Subject)); + + _observationsSets = _observationsSets.AddRange(missing); + + return observationsSets.Bind( + os => os.Observations.Filter(o => o.EvidenceSourceID == evidenceSourceID) + ); + } + } + + public SimObservationsSet GetObservationSet(string subject) + { + lock (_syncLock) + { + RequireTrue(IsSubject(subject)); + + var observationsSet = FindOrLoadOrCreateObservationsSet(subject); + + if (!_observationsSets.ContainsObservationsSet(observationsSet.Subject)) + { + _observationsSets = _observationsSets.Add(observationsSet); + } + + return observationsSet; + } + } + + public Set Subjects { get; private set; } + + public void Load(Simulation simulation) + { + lock (_syncLock) + { + RequireFalse(_pathToEvidenceDirectory.IsAString()); + RequireTrue(Subjects.IsEmpty); + RequireTrue(EvidenceSources.IsEmpty); + RequireTrue(_observationsSets.IsEmpty); + + var subjects = simulation.SimConfig.SimOutput.DependentVariables + .Map(e => e.Name); + + Subjects = toSet(subjects); + + _pathToEvidenceDirectory = simulation.GetPrivateDirectory(EVIDENCE_SUBDIRECTORY); + if (!Directory.Exists(_pathToEvidenceDirectory)) return; + + var pathToEvidenceSources = Combine(_pathToEvidenceDirectory, EVIDENCE_SOURCES_FILE_NAME); + if (!File.Exists(pathToEvidenceSources)) return; + + var dto = ReadFile(pathToEvidenceSources); + var evidenceSources = EvidenceSourcesDTO.FromDTO(dto); + EvidenceSources = evidenceSources; + } + } + + public void Unload() + { + lock (_syncLock) + { + _pathToEvidenceDirectory = default; + Subjects = default; + EvidenceSources = default; + _observationsSets = default; + } + } + + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + } + + _disposed = true; + } + } + + private static void Save(Arr evidenceSources, string pathToEvidenceDirectory) + { + var dto = EvidenceSourcesDTO.ToDTO(evidenceSources); + var pathToEvidenceSources = Combine(pathToEvidenceDirectory, EVIDENCE_SOURCES_FILE_NAME); + WriteFile(dto, pathToEvidenceSources); + } + + private Arr RemoveObservationsImpl(int evidenceSourceID, string subject) + { + var observationsSet = FindOrLoadOrCreateObservationsSet(subject); + + var removed = observationsSet.Observations.Filter(o => o.EvidenceSourceID == evidenceSourceID); + + if (!removed.IsEmpty) + { + observationsSet = SimObservationsSet.RemoveAllObservations(observationsSet, evidenceSourceID); + + SimObservationsSet.Save(observationsSet, _pathToEvidenceDirectory); + + var index = _observationsSets.FindIndex(os => os.Subject == subject); + + var observationsSets = index.IsFound() + ? _observationsSets.SetItem(index, observationsSet) + : _observationsSets.Add(observationsSet); + + _observationsSets = observationsSets; + } + + return removed; + } + + private SimObservationsSet FindOrLoadOrCreateObservationsSet(string subject) => + _observationsSets + .FindObservationsSet(subject) + .Match( + os => os, + () => SimObservationsSet.LoadOrCreate(_pathToEvidenceDirectory, subject) + ); + + private bool IsSubject(string subject) => + SimEvidenceExt.IsSubject(this, subject); + + private string _pathToEvidenceDirectory; + private Arr _observationsSets; + private readonly ISubject<(SimEvidenceSource SimEvidenceSource, ObservableQualifier Change)> _evidenceSourcesChangesSubject = + new Subject<(SimEvidenceSource SimEvidenceSource, ObservableQualifier Change)>(); + private readonly ISubject<(Arr SimObservations, ObservableQualifier Change)> _observationsChangesSubject = + new Subject<(Arr SimObservations, ObservableQualifier Change)>(); + private readonly object _syncLock = new object(); + private bool _disposed = false; + } +} diff --git a/RVis.Model/SimEvidence/SimEvidenceSource.cs b/RVis.Model/SimEvidence/SimEvidenceSource.cs new file mode 100644 index 0000000..3bea4b6 --- /dev/null +++ b/RVis.Model/SimEvidence/SimEvidenceSource.cs @@ -0,0 +1,61 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public readonly struct SimEvidenceSource : IEquatable + { + internal SimEvidenceSource( + int id, + string name, + string description, + Set subjects, + string refName, + string refHash + ) + { + RequireTrue(id >= 0); + RequireTrue(name.IsAString()); + RequireFalse(subjects.IsEmpty); + RequireTrue(refName.IsAString()); + RequireTrue(refHash.IsAString()); + + ID = id; + Name = name; + Description = description; + Subjects = subjects; + RefName = refName; + RefHash = refHash; + } + + public int ID { get; } + + public string Name { get; } + + public string Description { get; } + + public Set Subjects { get; } + + public string RefName { get; } + + public string RefHash { get; } + + public override bool Equals(object obj) => + obj is SimEvidenceSource evidenceSource && Equals(evidenceSource); + + public bool Equals(SimEvidenceSource other) => + (ID, Name, Description, Subjects, RefName, RefHash) == + (other.ID, other.Name, other.Description, other.Subjects, other.RefName, other.RefHash); + + public override int GetHashCode() => + (ID, Name, Description, Subjects, RefName, RefHash).GetHashCode(); + + public static bool operator ==(SimEvidenceSource lhs, SimEvidenceSource rhs) => + lhs.Equals(rhs); + + public static bool operator !=(SimEvidenceSource lhs, SimEvidenceSource rhs) => + !(lhs == rhs); + } +} diff --git a/RVis.Model/SimEvidence/SimObservations.cs b/RVis.Model/SimEvidence/SimObservations.cs new file mode 100644 index 0000000..80d83cb --- /dev/null +++ b/RVis.Model/SimEvidence/SimObservations.cs @@ -0,0 +1,63 @@ +using LanguageExt; +using RVis.Base.Extensions; +using System; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public readonly struct SimObservations : IEquatable + { + internal SimObservations( + int id, + int evidenceSourceID, + string subject, + string refName, + Arr x, + Arr y + ) + { + RequireTrue(id > 0); + RequireTrue(evidenceSourceID > 0); + RequireTrue(subject.IsAString()); + RequireTrue(refName.IsAString()); + RequireFalse(x.IsEmpty); + RequireFalse(y.IsEmpty); + RequireTrue(x.Count == y.Count); + + ID = id; + EvidenceSourceID = evidenceSourceID; + Subject = subject; + RefName = refName; + X = x; + Y = y; + } + + public int ID { get; } + + public int EvidenceSourceID { get; } + + public string Subject { get; } + + public string RefName { get; } + + public Arr X { get; } + + public Arr Y { get; } + + public override bool Equals(object obj) => + obj is SimObservations observations && Equals(observations); + + public bool Equals(SimObservations other) => + (ID, EvidenceSourceID, Subject, RefName, X, Y) == + (other.ID, other.EvidenceSourceID, other.Subject, other.RefName, other.X, other.Y); + + public override int GetHashCode() => + (ID, EvidenceSourceID, Subject, RefName, X, Y).GetHashCode(); + + public static bool operator ==(SimObservations lhs, SimObservations rhs) => + lhs.Equals(rhs); + + public static bool operator !=(SimObservations lhs, SimObservations rhs) => + !(lhs == rhs); + } +} diff --git a/RVis.Model/SimEvidence/SimObservationsSet.cs b/RVis.Model/SimEvidence/SimObservationsSet.cs new file mode 100644 index 0000000..f32b5de --- /dev/null +++ b/RVis.Model/SimEvidence/SimObservationsSet.cs @@ -0,0 +1,36 @@ +using LanguageExt; +using System; +using static RVis.Base.Check; + +namespace RVis.Model +{ + public readonly partial struct SimObservationsSet : IEquatable + { + internal SimObservationsSet(string subject, Arr observations) + { + RequireTrue(observations.ForAll(o => o.Subject == subject)); + + Subject = subject; + Observations = observations; + } + + public string Subject { get; } + + public Arr Observations { get; } + + public override bool Equals(object obj) => + obj is SimObservationsSet observationsSet && Equals(observationsSet); + + public bool Equals(SimObservationsSet other) => + (Subject, Observations) == (other.Subject, other.Observations); + + public override int GetHashCode() => + (Subject, Observations).GetHashCode(); + + public static bool operator ==(SimObservationsSet lhs, SimObservationsSet rhs) => + lhs.Equals(rhs); + + public static bool operator !=(SimObservationsSet lhs, SimObservationsSet rhs) => + !(lhs == rhs); + } +} diff --git a/RVis.sln b/RVis.sln new file mode 100644 index 0000000..ee1021d --- /dev/null +++ b/RVis.sln @@ -0,0 +1,351 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Version", "Version", "{727287DC-A17E-4743-B19C-BE8A6FF9E4B3}" + ProjectSection(SolutionItems) = preProject + Version\VersionInfo.cs = Version\VersionInfo.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVis.Base", "RVis.Base\RVis.Base.csproj", "{75DF8B2F-D179-436D-8107-BE640F3C3531}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVis.Data", "RVis.Data\RVis.Data.csproj", "{4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVis.Model", "RVis.Model\RVis.Model.csproj", "{3EDAA788-C299-4133-B59A-D9F3F13FA978}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{0D786350-D0C4-4F5A-ABC2-F6297B25E1F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVisUI.Model", "UI\RVisUI.Model\RVisUI.Model.csproj", "{3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVisUI.Mvvm", "UI\RVisUI.Mvvm\RVisUI.Mvvm.csproj", "{238E1DCB-2AA1-4233-8B90-53E43F6B6C27}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVisUI.Ioc", "UI\RVisUI.Ioc\RVisUI.Ioc.csproj", "{E10A54A2-08D1-435A-9B2E-F828A6A55662}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVisUI", "UI\RVisUI\RVisUI.csproj", "{D4C8E902-3493-4D47-B2AE-26A6911F9517}" + ProjectSection(ProjectDependencies) = postProject + {09AFDA37-D901-47B2-A19F-C8BF894E1338} = {09AFDA37-D901-47B2-A19F-C8BF894E1338} + {1A879972-B889-4F64-A88E-EF14C1B63DDC} = {1A879972-B889-4F64-A88E-EF14C1B63DDC} + {AB936B83-5699-4412-830C-8353C691F3C0} = {AB936B83-5699-4412-830C-8353C691F3C0} + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB} = {8CCB549C-60EB-422A-ABD8-0A37B3072EEB} + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1} = {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1} + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B} = {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WinR", "WinR", "{D9DDD46A-E3CE-47D1-82B1-0C717DBE5332}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVis.ROps", "WinR\RVis.ROps\RVis.ROps.csproj", "{A43377D5-7F30-4118-B1DD-2DDB57CBD399}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVis.Client", "WinR\RVis.Client\RVis.Client.csproj", "{C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "WinR.Shared", "WinR\WinR.Shared\WinR.Shared.shproj", "{3C465701-751B-4389-AF08-2571AB9EBD06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVis.Server", "WinR\RVis.Server\RVis.Server.csproj", "{6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module", "Module", "{00539EEB-CACE-4335-A0BA-7EED6EC088B6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plot", "UI\Module\Plot\Plot.csproj", "{1A879972-B889-4F64-A88E-EF14C1B63DDC}" + ProjectSection(ProjectDependencies) = postProject + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA} = {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA} + {11039298-B9BF-4B94-86BB-C6AA6AC8469F} = {11039298-B9BF-4B94-86BB-C6AA6AC8469F} + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5} = {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVisUI.Wpf", "UI\RVisUI.WPF\RVisUI.Wpf.csproj", "{306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{0FC737B6-E3F2-42C7-9011-9EA9680E5061}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVis.Base.Test", "Test\RVis.Base.Test\RVis.Base.Test.csproj", "{B5DCF730-7238-4841-8BF1-1E24CB226728}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RVis.Data.Test", "Test\RVis.Data.Test\RVis.Data.Test.csproj", "{123ACA74-C087-4806-8E69-0F867B7FDD75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVis.ROps.Test", "Test\RVis.ROps.Test\RVis.ROps.Test.csproj", "{DC958D41-14F0-4C4D-A5CA-430DDC42FA50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVis.Client.Test", "Test\RVis.Client.Test\RVis.Client.Test.csproj", "{321F566B-48CF-4745-9ECF-0262397B8EC5}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Test.Shared", "Test\Test.Shared\Test.Shared.shproj", "{C1042840-8833-473B-A4FD-162A68B04D74}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVis.Model.Test", "Test\RVis.Model.Test\RVis.Model.Test.csproj", "{7C5F15AC-34DC-42B0-A73C-4854B8F95D97}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Evidence", "UI\Module\Evidence\Evidence.csproj", "{8CCB549C-60EB-422A-ABD8-0A37B3072EEB}" + ProjectSection(ProjectDependencies) = postProject + {75DF8B2F-D179-436D-8107-BE640F3C3531} = {75DF8B2F-D179-436D-8107-BE640F3C3531} + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA} = {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA} + {3EDAA788-C299-4133-B59A-D9F3F13FA978} = {3EDAA788-C299-4133-B59A-D9F3F13FA978} + {11039298-B9BF-4B94-86BB-C6AA6AC8469F} = {11039298-B9BF-4B94-86BB-C6AA6AC8469F} + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5} = {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5} + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D} = {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sampling", "UI\Module\Sampling\Sampling.csproj", "{0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}" + ProjectSection(ProjectDependencies) = postProject + {11039298-B9BF-4B94-86BB-C6AA6AC8469F} = {11039298-B9BF-4B94-86BB-C6AA6AC8469F} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVisUI.AppInf", "UI\RVisUI.AppInf\RVisUI.AppInf.csproj", "{11039298-B9BF-4B94-86BB-C6AA6AC8469F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sensitivity", "UI\Module\Sensitivity\Sensitivity.csproj", "{09AFDA37-D901-47B2-A19F-C8BF894E1338}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Estimation", "UI\Module\Estimation\Estimation.csproj", "{AB936B83-5699-4412-830C-8353C691F3C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6F7F861D-5083-436D-A074-00D130A2B63A}" + ProjectSection(SolutionItems) = preProject + azure-pipelines.yml = azure-pipelines.yml + clean.bat = clean.bat + clean_x64.bat = clean_x64.bat + Directory.Build.props = Directory.Build.props + prepare_archive.bat = prepare_archive.bat + prepare_archive_x64.bat = prepare_archive_x64.bat + prepare_release.bat = prepare_release.bat + release_notes.md = release_notes.md + SetAppVerVariables.ps1 = SetAppVerVariables.ps1 + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RVisUI.Model.Test", "Test\RVisUI.Model.Test\RVisUI.Model.Test.csproj", "{1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Estimation.Test", "Test\Estimation.Test\Estimation.Test.csproj", "{552CCDB7-BA4A-44AB-ADCD-A96951B5767A}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Test\Test.Shared\Test.Shared.projitems*{321f566b-48cf-4745-9ecf-0262397b8ec5}*SharedItemsImports = 4 + WinR\WinR.Shared\WinR.Shared.projitems*{3c465701-751b-4389-af08-2571ab9ebd06}*SharedItemsImports = 13 + Test\Test.Shared\Test.Shared.projitems*{552ccdb7-ba4a-44ab-adcd-a96951b5767a}*SharedItemsImports = 4 + WinR\WinR.Shared\WinR.Shared.projitems*{6e77e4d9-4ca7-43ca-92c0-1914ca4997d1}*SharedItemsImports = 4 + Test\Test.Shared\Test.Shared.projitems*{7c5f15ac-34dc-42b0-a73c-4854b8f95d97}*SharedItemsImports = 4 + Test\Test.Shared\Test.Shared.projitems*{c1042840-8833-473b-a4fd-162a68b04d74}*SharedItemsImports = 13 + WinR\WinR.Shared\WinR.Shared.projitems*{c538987a-1dad-48ca-a3dd-cdd00f75aede}*SharedItemsImports = 4 + Test\Test.Shared\Test.Shared.projitems*{dc958d41-14f0-4c4d-a5ca-430ddc42fa50}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Debug|x64.ActiveCfg = Debug|x64 + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Debug|x64.Build.0 = Debug|x64 + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Release|Any CPU.Build.0 = Release|Any CPU + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Release|x64.ActiveCfg = Release|x64 + {75DF8B2F-D179-436D-8107-BE640F3C3531}.Release|x64.Build.0 = Release|x64 + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Debug|x64.ActiveCfg = Debug|x64 + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Debug|x64.Build.0 = Debug|x64 + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Release|Any CPU.Build.0 = Release|Any CPU + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Release|x64.ActiveCfg = Release|x64 + {4DEE84DC-AB9E-4AE7-9021-2C0FAB41964D}.Release|x64.Build.0 = Release|x64 + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Debug|x64.ActiveCfg = Debug|x64 + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Debug|x64.Build.0 = Debug|x64 + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Release|Any CPU.Build.0 = Release|Any CPU + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Release|x64.ActiveCfg = Release|x64 + {3EDAA788-C299-4133-B59A-D9F3F13FA978}.Release|x64.Build.0 = Release|x64 + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Debug|x64.ActiveCfg = Debug|x64 + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Debug|x64.Build.0 = Debug|x64 + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Release|Any CPU.Build.0 = Release|Any CPU + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Release|x64.ActiveCfg = Release|x64 + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA}.Release|x64.Build.0 = Release|x64 + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Debug|x64.ActiveCfg = Debug|x64 + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Debug|x64.Build.0 = Debug|x64 + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Release|Any CPU.Build.0 = Release|Any CPU + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Release|x64.ActiveCfg = Release|x64 + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27}.Release|x64.Build.0 = Release|x64 + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Debug|x64.ActiveCfg = Debug|x64 + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Debug|x64.Build.0 = Debug|x64 + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Release|Any CPU.Build.0 = Release|Any CPU + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Release|x64.ActiveCfg = Release|x64 + {E10A54A2-08D1-435A-9B2E-F828A6A55662}.Release|x64.Build.0 = Release|x64 + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Debug|x64.ActiveCfg = Debug|x64 + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Debug|x64.Build.0 = Debug|x64 + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Release|Any CPU.Build.0 = Release|Any CPU + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Release|x64.ActiveCfg = Release|x64 + {D4C8E902-3493-4D47-B2AE-26A6911F9517}.Release|x64.Build.0 = Release|x64 + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Debug|x64.ActiveCfg = Debug|x64 + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Debug|x64.Build.0 = Debug|x64 + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Release|Any CPU.Build.0 = Release|Any CPU + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Release|x64.ActiveCfg = Release|x64 + {A43377D5-7F30-4118-B1DD-2DDB57CBD399}.Release|x64.Build.0 = Release|x64 + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Debug|x64.ActiveCfg = Debug|x64 + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Debug|x64.Build.0 = Debug|x64 + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Release|Any CPU.Build.0 = Release|Any CPU + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Release|x64.ActiveCfg = Release|x64 + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE}.Release|x64.Build.0 = Release|x64 + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Debug|x64.ActiveCfg = Debug|x64 + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Debug|x64.Build.0 = Debug|x64 + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Release|Any CPU.Build.0 = Release|Any CPU + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Release|x64.ActiveCfg = Release|x64 + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1}.Release|x64.Build.0 = Release|x64 + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Debug|x64.ActiveCfg = Debug|x64 + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Debug|x64.Build.0 = Debug|x64 + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Release|Any CPU.Build.0 = Release|Any CPU + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Release|x64.ActiveCfg = Release|x64 + {1A879972-B889-4F64-A88E-EF14C1B63DDC}.Release|x64.Build.0 = Release|x64 + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Debug|x64.ActiveCfg = Debug|x64 + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Debug|x64.Build.0 = Debug|x64 + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Release|Any CPU.Build.0 = Release|Any CPU + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Release|x64.ActiveCfg = Release|x64 + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5}.Release|x64.Build.0 = Release|x64 + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Debug|x64.ActiveCfg = Debug|x64 + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Debug|x64.Build.0 = Debug|x64 + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Release|Any CPU.Build.0 = Release|Any CPU + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Release|x64.ActiveCfg = Release|x64 + {B5DCF730-7238-4841-8BF1-1E24CB226728}.Release|x64.Build.0 = Release|x64 + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Debug|x64.ActiveCfg = Debug|x64 + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Debug|x64.Build.0 = Debug|x64 + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Release|Any CPU.Build.0 = Release|Any CPU + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Release|x64.ActiveCfg = Release|x64 + {123ACA74-C087-4806-8E69-0F867B7FDD75}.Release|x64.Build.0 = Release|x64 + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Debug|x64.ActiveCfg = Debug|x64 + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Debug|x64.Build.0 = Debug|x64 + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Release|Any CPU.Build.0 = Release|Any CPU + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Release|x64.ActiveCfg = Release|x64 + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50}.Release|x64.Build.0 = Release|x64 + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Debug|x64.ActiveCfg = Debug|x64 + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Debug|x64.Build.0 = Debug|x64 + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Release|Any CPU.Build.0 = Release|Any CPU + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Release|x64.ActiveCfg = Release|x64 + {321F566B-48CF-4745-9ECF-0262397B8EC5}.Release|x64.Build.0 = Release|x64 + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Debug|x64.ActiveCfg = Debug|x64 + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Debug|x64.Build.0 = Debug|x64 + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Release|Any CPU.Build.0 = Release|Any CPU + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Release|x64.ActiveCfg = Release|x64 + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97}.Release|x64.Build.0 = Release|x64 + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Debug|x64.ActiveCfg = Debug|x64 + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Debug|x64.Build.0 = Debug|x64 + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Release|Any CPU.Build.0 = Release|Any CPU + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Release|x64.ActiveCfg = Release|x64 + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB}.Release|x64.Build.0 = Release|x64 + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Debug|x64.ActiveCfg = Debug|x64 + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Debug|x64.Build.0 = Debug|x64 + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Release|Any CPU.Build.0 = Release|Any CPU + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Release|x64.ActiveCfg = Release|x64 + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B}.Release|x64.Build.0 = Release|x64 + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Debug|x64.ActiveCfg = Debug|x64 + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Debug|x64.Build.0 = Debug|x64 + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Release|Any CPU.Build.0 = Release|Any CPU + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Release|x64.ActiveCfg = Release|x64 + {11039298-B9BF-4B94-86BB-C6AA6AC8469F}.Release|x64.Build.0 = Release|x64 + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Debug|x64.ActiveCfg = Debug|x64 + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Debug|x64.Build.0 = Debug|x64 + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Release|Any CPU.Build.0 = Release|Any CPU + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Release|x64.ActiveCfg = Release|x64 + {09AFDA37-D901-47B2-A19F-C8BF894E1338}.Release|x64.Build.0 = Release|x64 + {AB936B83-5699-4412-830C-8353C691F3C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB936B83-5699-4412-830C-8353C691F3C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB936B83-5699-4412-830C-8353C691F3C0}.Debug|x64.ActiveCfg = Debug|x64 + {AB936B83-5699-4412-830C-8353C691F3C0}.Debug|x64.Build.0 = Debug|x64 + {AB936B83-5699-4412-830C-8353C691F3C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB936B83-5699-4412-830C-8353C691F3C0}.Release|Any CPU.Build.0 = Release|Any CPU + {AB936B83-5699-4412-830C-8353C691F3C0}.Release|x64.ActiveCfg = Release|x64 + {AB936B83-5699-4412-830C-8353C691F3C0}.Release|x64.Build.0 = Release|x64 + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Debug|x64.Build.0 = Debug|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Release|Any CPU.Build.0 = Release|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Release|x64.ActiveCfg = Release|Any CPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E}.Release|x64.Build.0 = Release|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Debug|x64.ActiveCfg = Debug|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Debug|x64.Build.0 = Debug|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Release|Any CPU.Build.0 = Release|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Release|x64.ActiveCfg = Release|Any CPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {238E1DCB-2AA1-4233-8B90-53E43F6B6C27} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {E10A54A2-08D1-435A-9B2E-F828A6A55662} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {D4C8E902-3493-4D47-B2AE-26A6911F9517} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {A43377D5-7F30-4118-B1DD-2DDB57CBD399} = {D9DDD46A-E3CE-47D1-82B1-0C717DBE5332} + {C538987A-1DAD-48CA-A3DD-CDD00F75AEDE} = {D9DDD46A-E3CE-47D1-82B1-0C717DBE5332} + {3C465701-751B-4389-AF08-2571AB9EBD06} = {D9DDD46A-E3CE-47D1-82B1-0C717DBE5332} + {6E77E4D9-4CA7-43CA-92C0-1914CA4997D1} = {D9DDD46A-E3CE-47D1-82B1-0C717DBE5332} + {00539EEB-CACE-4335-A0BA-7EED6EC088B6} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {1A879972-B889-4F64-A88E-EF14C1B63DDC} = {00539EEB-CACE-4335-A0BA-7EED6EC088B6} + {306C45CD-7A5F-4EBD-BE4B-8BE5DEDDAEA5} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {B5DCF730-7238-4841-8BF1-1E24CB226728} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {123ACA74-C087-4806-8E69-0F867B7FDD75} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {321F566B-48CF-4745-9ECF-0262397B8EC5} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {C1042840-8833-473B-A4FD-162A68B04D74} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {8CCB549C-60EB-422A-ABD8-0A37B3072EEB} = {00539EEB-CACE-4335-A0BA-7EED6EC088B6} + {0D0B90E8-9F63-4FE7-A3BD-14D2C868DB7B} = {00539EEB-CACE-4335-A0BA-7EED6EC088B6} + {11039298-B9BF-4B94-86BB-C6AA6AC8469F} = {0D786350-D0C4-4F5A-ABC2-F6297B25E1F7} + {09AFDA37-D901-47B2-A19F-C8BF894E1338} = {00539EEB-CACE-4335-A0BA-7EED6EC088B6} + {AB936B83-5699-4412-830C-8353C691F3C0} = {00539EEB-CACE-4335-A0BA-7EED6EC088B6} + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A} = {0FC737B6-E3F2-42C7-9011-9EA9680E5061} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EF372732-087C-472B-A797-123B7A1B7C34} + EndGlobalSection +EndGlobal diff --git a/SetAppVerVariables.ps1 b/SetAppVerVariables.ps1 new file mode 100644 index 0000000..77becb3 --- /dev/null +++ b/SetAppVerVariables.ps1 @@ -0,0 +1,7 @@ +$pathToExe = "$((Get-Location).Path)\UI\RVisUI\bin\Release\RVisUI.exe" +$fileVersionInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($pathToExe) +Write-Host "##vso[task.setvariable variable=AppVer]$($fileVersionInfo.FileVersion)" +Write-Host "##vso[task.setvariable variable=AppVerMajor]$($fileVersionInfo.FileMajorPart)" +Write-Host "##vso[task.setvariable variable=AppVerMinor]$($fileVersionInfo.FileMinorPart)" +Write-Host "##vso[task.setvariable variable=AppVerBuild]$($fileVersionInfo.FileBuildPart)" +Write-Host "##vso[task.setvariable variable=AppVerPrivate]$($fileVersionInfo.FilePrivatePart)" diff --git a/Test/Estimation.Test/ErrorModel/Impl/ErrorModelImpl.cs b/Test/Estimation.Test/ErrorModel/Impl/ErrorModelImpl.cs new file mode 100644 index 0000000..7ce22ae --- /dev/null +++ b/Test/Estimation.Test/ErrorModel/Impl/ErrorModelImpl.cs @@ -0,0 +1,19 @@ +using RVis.Client; +using RVis.Data; + +namespace Estimation.Test +{ + internal static class ErrorModelImpl + { + internal static NumDataColumn[] GetNumData(string code) + { + using (var server = new RVisServer()) + { + using (var client = server.OpenChannel()) + { + return client.EvaluateNumData(code); + } + } + } + } +} diff --git a/Test/Estimation.Test/ErrorModel/Impl/UnitTestTruncNorm.cs b/Test/Estimation.Test/ErrorModel/Impl/UnitTestTruncNorm.cs new file mode 100644 index 0000000..4c84ef0 --- /dev/null +++ b/Test/Estimation.Test/ErrorModel/Impl/UnitTestTruncNorm.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static Estimation.Test.ErrorModelImpl; +using static RVis.Base.Constant; + +namespace Estimation.Test +{ + [TestClass] + public class UnitTestTruncNorm + { +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestDTruncNorm() + { + // arrange + var lower = -0.5; + var upper = 3d; + var mean = 1d; + var standardDeviation = 1d; + + var x1 = mean; + var x2 = lower - 1d; + var x3 = upper + 1d; + + var expected1 = GetNumData($"truncnorm::dtruncnorm({x1},{lower},{upper},{mean},{standardDeviation})")[0].Data[0]; + var expected2 = GetNumData($"truncnorm::dtruncnorm({x2},{lower},{upper},{mean},{standardDeviation})")[0].Data[0]; + var expected3 = GetNumData($"truncnorm::dtruncnorm({x3},{lower},{upper},{mean},{standardDeviation})")[0].Data[0]; + + // act + var actual1 = TruncNorm.DTruncNorm(x1, lower, upper, mean, standardDeviation); + var actual2 = TruncNorm.DTruncNorm(x2, lower, upper, mean, standardDeviation); + var actual3 = TruncNorm.DTruncNorm(x3, lower, upper, mean, standardDeviation); + + // assert + Assert.AreEqual(expected1, actual1, TOLERANCE); + Assert.AreEqual(expected2, actual2, TOLERANCE); + Assert.AreEqual(expected3, actual3, TOLERANCE); + } +#endif + } +} diff --git a/Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticExpErrorModel.cs b/Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticExpErrorModel.cs new file mode 100644 index 0000000..f6bad0a --- /dev/null +++ b/Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticExpErrorModel.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static RVis.Base.Check; +using static RVis.Base.Constant; + +namespace Estimation.Test +{ + [TestClass] + public class UnitTestHeteroscedasticExpErrorModel + { + [TestMethod] + public void TestSerializationRoundTrip() + { + // arrange + var expected = new HeteroscedasticExpErrorModel(1d, 0.1, 2d, 0.2, 3d); + + // act + var serialized = expected.ToString(); + var deserialized = ErrorModel.DeserializeErrorModel(serialized); + var errorModel = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + var expectedPerturbed = RequireInstanceOf(expected.GetPerturbed(default)); + var serializedPerturbed = expectedPerturbed.ToString(); + var deserializedPerturbed = ErrorModel.DeserializeErrorModel(serializedPerturbed); + var errorModelPerturbed = deserializedPerturbed.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = RequireInstanceOf(errorModel); + Assert.AreEqual(expected, actual); + + var actualPerturbed = RequireInstanceOf(errorModelPerturbed); + Assert.AreEqual(expectedPerturbed.Delta, actualPerturbed.Delta, TOLERANCE); + Assert.AreEqual(expectedPerturbed.DeltaStep, actualPerturbed.DeltaStep, TOLERANCE); + Assert.AreEqual(expectedPerturbed.DeltaStepInitializer, actualPerturbed.DeltaStepInitializer, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Sigma, actualPerturbed.Sigma, TOLERANCE); + Assert.AreEqual(expectedPerturbed.SigmaStep, actualPerturbed.SigmaStep, TOLERANCE); + Assert.AreEqual(expectedPerturbed.SigmaStepInitializer, actualPerturbed.SigmaStepInitializer, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Lower, actualPerturbed.Lower, TOLERANCE); + } + } +} diff --git a/Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticPowerErrorModel.cs b/Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticPowerErrorModel.cs new file mode 100644 index 0000000..4c9d767 --- /dev/null +++ b/Test/Estimation.Test/ErrorModel/UnitTestHeteroscedasticPowerErrorModel.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static RVis.Base.Check; +using static RVis.Base.Constant; + +namespace Estimation.Test +{ + [TestClass] + public class UnitTestHeteroscedasticPowerErrorModel + { + [TestMethod] + public void TestSerializationRoundTrip() + { + // arrange + var expected = new HeteroscedasticPowerErrorModel(1d, 0.1, 2d, 0.2, 3d, 0.3, 4d); + + // act + var serialized = expected.ToString(); + var deserialized = ErrorModel.DeserializeErrorModel(serialized); + var errorModel = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + var expectedPerturbed = RequireInstanceOf(expected.GetPerturbed(default)); + var serializedPerturbed = expectedPerturbed.ToString(); + var deserializedPerturbed = ErrorModel.DeserializeErrorModel(serializedPerturbed); + var errorModelPerturbed = deserializedPerturbed.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = RequireInstanceOf(errorModel); + Assert.AreEqual(expected, actual); + + var actualPerturbed = RequireInstanceOf(errorModelPerturbed); + Assert.AreEqual(expectedPerturbed.Delta1, actualPerturbed.Delta1, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Delta1Step, actualPerturbed.Delta1Step, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Delta1StepInitializer, actualPerturbed.Delta1StepInitializer, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Delta2, actualPerturbed.Delta2, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Delta2Step, actualPerturbed.Delta2Step, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Delta2StepInitializer, actualPerturbed.Delta2StepInitializer, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Sigma, actualPerturbed.Sigma, TOLERANCE); + Assert.AreEqual(expectedPerturbed.SigmaStep, actualPerturbed.SigmaStep, TOLERANCE); + Assert.AreEqual(expectedPerturbed.SigmaStepInitializer, actualPerturbed.SigmaStepInitializer, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Lower, actualPerturbed.Lower, TOLERANCE); + } + } +} diff --git a/Test/Estimation.Test/ErrorModel/UnitTestLogNormalErrorModel.cs b/Test/Estimation.Test/ErrorModel/UnitTestLogNormalErrorModel.cs new file mode 100644 index 0000000..7280d57 --- /dev/null +++ b/Test/Estimation.Test/ErrorModel/UnitTestLogNormalErrorModel.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static RVis.Base.Check; +using static RVis.Base.Constant; + +namespace Estimation.Test +{ + [TestClass] + public class UnitTestLogNormalErrorModel + { + [TestMethod] + public void TestSerializationRoundTrip() + { + // arrange + var expected = new LogNormalErrorModel(1d, 0.1); + + // act + var serialized = expected.ToString(); + var deserialized = ErrorModel.DeserializeErrorModel(serialized); + var errorModel = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + var expectedPerturbed = RequireInstanceOf(expected.GetPerturbed(default)); + var serializedPerturbed = expectedPerturbed.ToString(); + var deserializedPerturbed = ErrorModel.DeserializeErrorModel(serializedPerturbed); + var errorModelPerturbed = deserializedPerturbed.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = RequireInstanceOf(errorModel); + Assert.AreEqual(expected, actual); + + var actualPerturbed = RequireInstanceOf(errorModelPerturbed); + Assert.AreEqual(expectedPerturbed.SigmaLog, actualPerturbed.SigmaLog, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Step, actualPerturbed.Step, TOLERANCE); + Assert.AreEqual(expectedPerturbed.StepInitializer, actualPerturbed.StepInitializer, TOLERANCE); + } + } +} diff --git a/Test/Estimation.Test/ErrorModel/UnitTestNormalErrorModel.cs b/Test/Estimation.Test/ErrorModel/UnitTestNormalErrorModel.cs new file mode 100644 index 0000000..600d4b6 --- /dev/null +++ b/Test/Estimation.Test/ErrorModel/UnitTestNormalErrorModel.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static RVis.Base.Check; +using static RVis.Base.Constant; + +namespace Estimation.Test +{ + [TestClass] + public class UnitTestNormalErrorModel + { + [TestMethod] + public void TestSerializationRoundTrip() + { + // arrange + var expected = new NormalErrorModel(1d, 0.1); + + // act + var serialized = expected.ToString(); + var deserialized = ErrorModel.DeserializeErrorModel(serialized); + var errorModel = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + var expectedPerturbed = RequireInstanceOf(expected.GetPerturbed(default)); + var serializedPerturbed = expectedPerturbed.ToString(); + var deserializedPerturbed = ErrorModel.DeserializeErrorModel(serializedPerturbed); + var errorModelPerturbed = deserializedPerturbed.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = RequireInstanceOf(errorModel); + Assert.AreEqual(expected, actual); + + var actualPerturbed = RequireInstanceOf(errorModelPerturbed); + Assert.AreEqual(expectedPerturbed.Sigma, actualPerturbed.Sigma, TOLERANCE); + Assert.AreEqual(expectedPerturbed.Step, actualPerturbed.Step, TOLERANCE); + Assert.AreEqual(expectedPerturbed.StepInitializer, actualPerturbed.StepInitializer, TOLERANCE); + } + } +} diff --git a/Test/Estimation.Test/Estimation.Test.csproj b/Test/Estimation.Test/Estimation.Test.csproj new file mode 100644 index 0000000..83aeac0 --- /dev/null +++ b/Test/Estimation.Test/Estimation.Test.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {552CCDB7-BA4A-44AB-ADCD-A96951B5767A} + Library + Properties + Estimation.Test + Estimation.Test + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + + + + + + + 3.3.2 + + + 1.4.0 + + + 1.4.0 + + + + + {75df8b2f-d179-436d-8107-be640f3c3531} + RVis.Base + + + {4dee84dc-ab9e-4ae7-9021-2c0fab41964d} + RVis.Data + + + {3edaa788-c299-4133-b59a-d9f3f13fa978} + RVis.Model + + + {ab936b83-5699-4412-830c-8353c691f3c0} + Estimation + + + {c538987a-1dad-48ca-a3dd-cdd00f75aede} + RVis.Client + + + + + + \ No newline at end of file diff --git a/Test/Estimation.Test/Model/Extensions/UnitTestCollExt.cs b/Test/Estimation.Test/Model/Extensions/UnitTestCollExt.cs new file mode 100644 index 0000000..b782bd3 --- /dev/null +++ b/Test/Estimation.Test/Model/Extensions/UnitTestCollExt.cs @@ -0,0 +1,57 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; +using static RVis.Base.Constant; + +namespace Estimation.Test +{ + [TestClass] + public class UnitTestCollExt + { + [TestMethod] + public void TestSelectNearestY() + { + // arrange + var sourceX = new[] { 1d, 3d, 5d }; + var sourceY = new[] { 7d, 8d, 9d }; + + var targetX1 = new double[0]; + var expectedY1 = Enumerable.Empty(); + + var targetX2 = new[] { 1d, 3d, 5d }; + var expectedY2 = new[] { 7d, 8d, 9d }; + + var targetX3 = new[] { 0d, 10d }; + var expectedY3 = new[] { 7d, 9d }; + + var targetX4 = new[] { 0.9d, 2.9d, 4.9d }; + var expectedY4 = new[] { 7d, 8d, 9d }; + + var targetX5 = new[] { 1.1d, 3.1d, 5.1d }; + var expectedY5 = new[] { 7d, 8d, 9d }; + + var targetX6 = new[] { 1.1d, 4d, 5d }; + var expectedY6 = new[] { 7d, 8d, 9d }; + + var targetX7 = new[] { 1.1d, 4d + TOLERANCE, 5d }; + var expectedY7 = new[] { 7d, 8d, 9d }; + + // act + var actualY1 = targetX1.SelectNearestY(sourceX, sourceY); + var actualY2 = targetX2.SelectNearestY(sourceX, sourceY); + var actualY3 = targetX3.SelectNearestY(sourceX, sourceY); + var actualY4 = targetX4.SelectNearestY(sourceX, sourceY); + var actualY5 = targetX5.SelectNearestY(sourceX, sourceY); + var actualY6 = targetX6.SelectNearestY(sourceX, sourceY); + + // assert + Assert.IsTrue(expectedY1.SequenceEqual(actualY1)); + Assert.IsTrue(expectedY2.SequenceEqual(actualY2)); + Assert.IsTrue(expectedY3.SequenceEqual(actualY3)); + Assert.IsTrue(expectedY4.SequenceEqual(actualY4)); + Assert.IsTrue(expectedY5.SequenceEqual(actualY5)); + Assert.IsTrue(expectedY6.SequenceEqual(actualY6)); + Assert.ThrowsException(() => targetX7.SelectNearestY(sourceX, sourceY)); + } + } +} diff --git a/Test/Estimation.Test/Properties/AssemblyInfo.cs b/Test/Estimation.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8679ea9 --- /dev/null +++ b/Test/Estimation.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Estimation.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Estimation.Test")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("552ccdb7-ba4a-44ab-adcd-a96951b5767a")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Test/RVis.Base.Test/Extensions/UnitTestArrayExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestArrayExt.cs new file mode 100644 index 0000000..0e57c01 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestArrayExt.cs @@ -0,0 +1,39 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System; + +namespace RVis.Base.Test +{ + [TestClass()] + public class UnitTestArrayExt + { + [TestMethod()] + public void TestIsNullOrEmpty() + { + // arrange + object[] subject1 = default; + object[] subject2 = Array.Empty(); + + // act + var isNull = subject1.IsNullOrEmpty(); + var isEmpty = subject2.IsNullOrEmpty(); + + // assert + Assert.IsTrue(isNull); + Assert.IsTrue(isEmpty); + } + + [TestMethod()] + public void TestIsEmpty() + { + // arrange + object[] subject = Array.Empty(); + + // act + var isEmpty = subject.IsEmpty(); + + // assert + Assert.IsTrue(isEmpty); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Base.Test/Extensions/UnitTestBoolExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestBoolExt.cs new file mode 100644 index 0000000..1e04944 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestBoolExt.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; + +namespace RVis.Base.Test +{ + [TestClass()] + public class UnitTestBoolExt + { + [TestMethod()] + public void TestIsTrue() + { + // arrange + bool? subject1 = true; + bool? subject2 = false; + bool? subject3 = default; + + // act + var is1 = subject1.IsTrue(); + var is2 = subject2.IsTrue(); + var is3 = subject3.IsTrue(); + + // assert + Assert.IsTrue(is1); + Assert.IsFalse(is2); + Assert.IsFalse(is3); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Base.Test/Extensions/UnitTestCollExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestCollExt.cs new file mode 100644 index 0000000..d4481d5 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestCollExt.cs @@ -0,0 +1,193 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace RVis.Base.Test +{ + [TestClass()] + public class UnitTestCollExt + { + [TestMethod()] + public void TestFindIndex() + { + // arrange + var subject = new[] { 1, 2, 3, 4, 4, 3, 2, 1 }; + var toFind1 = 4; + var expected1 = 3; + var toFind2 = 9; + var expected2 = -1; + + // act + var actual1 = subject.FindIndex(i => i == toFind1); + var actual2 = subject.FindIndex(i => i == toFind2); + + // assert + Assert.AreEqual(expected1, actual1); + Assert.AreEqual(expected2, actual2); + Assert.AreEqual(-1, Array.Empty().FindIndex(i => i == default)); + } + + [TestMethod()] + public void TestIsCollection() + { + // arrange + var subject1 = new Collection(); + var subject2 = new Collection(new[] { 1, 2, 3 }); + Collection subject3 = default; + + // act + var isColl1 = subject1.IsCollection(); + var isColl2 = subject2.IsCollection(); + var isColl3 = subject3.IsCollection(); + + // assert + Assert.IsFalse(isColl1); + Assert.IsTrue(isColl2); + Assert.IsFalse(isColl3); + } + + [TestMethod()] + public void TestIsEmpty() + { + // arrange + var subject1 = new Collection(); + var subject2 = new Collection(new[] { 1, 2, 3 }); + + // act + var isEmpty1 = subject1.IsEmpty(); + var isEmpty2 = subject2.IsEmpty(); + + // assert + Assert.IsTrue(isEmpty1); + Assert.IsFalse(isEmpty2); + } + + [TestMethod()] + public void TestRemoveIf() + { + // arrange + var subject = new List(new[] { 1, 2, 3, 4, 5 }); + var toRemove = new[] { 3, 4 }; + var expected = new[] { 1, 2, 5 }; + + // act + subject.RemoveIf(i => toRemove.Contains(i)); + + // assert + Assert.IsTrue(subject.SequenceEqual(expected)); + } + + [TestMethod()] + public void TestMaxIndex() + { + // arrange + var subject = new[] { 1, 2, 3, 4, 4, 3, 2, 1 }; + var expected = 3; + + // act + var actual = subject.MaxIndex(); + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod()] + public void TestMinIndex() + { + // arrange + var subject = new[] { 1, 2, 3, 4, 4, 3, 2, 1 }; + var expected = 0; + + // act + var actual = subject.MinIndex(); + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod()] + public void TestAllSame() + { + // arrange + var subject = new[] { (123,1), (123,2), (123,3) }; + + // act + var allSame = subject.AllSame(t => t.Item1); + + // assert + Assert.IsTrue(allSame); + } + + [TestMethod()] + public void TestNotAllSame() + { + var subject = new[] { (123, 1), (123, 2), (321, 3) }; + + // act + var notAllSame = subject.NotAllSame(t => t.Item1); + + // assert + Assert.IsTrue(notAllSame); + } + + [TestMethod()] + public void TestAllUnique() + { + // arrange + var subject = new[] { (123, 1), (123, 2), (123, 3) }; + + // act + var allUnique = subject.AllUnique(t => t.Item2); + + // assert + Assert.IsTrue(allUnique); + } + + [TestMethod()] + public void TestNotAllUnique() + { + // arrange + var subject = new[] { (123, 1), (123, 2), (321, 3) }; + + // act + var notAllUnique = subject.NotAllUnique(t => t.Item1); + + // assert + Assert.IsTrue(notAllUnique); + } + + [TestMethod()] + public void TestAdd() + { + // arrange + var subject = new Dictionary { (123, 1), (456, 2), (789, 3) }; + var toAdd = (0, 0); + + // act + subject.Add(toAdd); + + // assert + Assert.IsTrue(subject.Count == 4); + Assert.IsTrue(subject[toAdd.Item1] == toAdd.Item2); + } + + [TestMethod()] + public void TestAddRange() + { + // arrange + var subject = new Dictionary { (123, 1), (456, 2), (789, 3) }; + var toAdd = new[] { (0, 0), (1, 1), (2, 2) }; + + // act + subject.AddRange(toAdd); + + // assert + Assert.IsTrue(subject.Count == 6); + Assert.IsTrue(toAdd.All(t => subject.ContainsKey(t.Item1))); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Base.Test/Extensions/UnitTestEnumExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestEnumExt.cs new file mode 100644 index 0000000..185722a --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestEnumExt.cs @@ -0,0 +1,113 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System; +using System.Linq; +using DescriptionAttribute = System.ComponentModel.DescriptionAttribute; + +namespace RVis.Base.Tests +{ + [TestClass()] + public class UnitTestEnumExt + { + [Flags] + private enum TestEnum + { + f1 = 0b0001, + + [Description("Test")] + f2 = 0b0010, + + f3 = 0b0100 + } + + [TestMethod()] + public void TestGetFlags() + { + // arrange + var expected = new[] { 1, 2, 4 }; + + // act + var actual = EnumExt.GetFlags().Cast(); + + // assert + Assert.IsTrue(expected.SequenceEqual(actual)); + } + + [TestMethod()] + public void TestIsAdd() + { + // arrange + var subject1 = ObservableQualifier.Add | ObservableQualifier.Change | ObservableQualifier.Remove; + var subject2 = ObservableQualifier.Change | ObservableQualifier.Remove; + + // act + var isAdd1 = subject1.IsAdd(); + var isAdd2 = subject2.IsAdd(); + + // assert + Assert.IsTrue(isAdd1); + Assert.IsFalse(isAdd2); + } + + [TestMethod()] + public void TestIsChange() + { + // arrange + var subject1 = ObservableQualifier.Add | ObservableQualifier.Change | ObservableQualifier.Remove; + var subject2 = ObservableQualifier.Add | ObservableQualifier.Remove; + + // act + var isChange1 = subject1.IsChange(); + var isChange2 = subject2.IsChange(); + + // assert + Assert.IsTrue(isChange1); + Assert.IsFalse(isChange2); + } + + [TestMethod()] + public void TestIsRemove() + { + // arrange + var subject1 = ObservableQualifier.Add | ObservableQualifier.Change | ObservableQualifier.Remove; + var subject2 = ObservableQualifier.Add | ObservableQualifier.Change; + + // act + var isRemove1 = subject1.IsRemove(); + var isRemove2 = subject2.IsRemove(); + + // assert + Assert.IsTrue(isRemove1); + Assert.IsFalse(isRemove2); + } + + [TestMethod()] + public void TestIsAddOrChange() + { + // arrange + var subject1 = ObservableQualifier.Add | ObservableQualifier.Change | ObservableQualifier.Remove; + var subject2 = ObservableQualifier.Remove; + + // act + var is1 = subject1.IsAddOrChange(); + var is2 = subject2.IsAddOrChange(); + + // assert + Assert.IsTrue(is1); + Assert.IsFalse(is2); + } + + [TestMethod()] + public void TestGetDescription() + { + // arrange + var expected = "Test"; + + // act + var actual = TestEnum.f2.GetDescription(); + + // assert + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Base.Test/Extensions/UnitTestFxExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestFxExt.cs new file mode 100644 index 0000000..c7fd9f8 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestFxExt.cs @@ -0,0 +1,129 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.IO; +using System.Reflection; + +namespace RVis.Base.Extensions.Tests +{ + [TestClass()] + public class UnitTestFxExt + { + private static string NormalizePath(string path) => + Path.GetFullPath(new Uri(path).LocalPath) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .ToUpperInvariant(); + + [TestMethod()] + public void TestHasNoSchema() + { + // arrange + var subject = new DataTable(); + + // act + var hasNoSchema1 = subject.HasNoSchema(); + subject.Columns.Add(new DataColumn()); + var hasNoSchema2 = subject.HasNoSchema(); + + // assert + Assert.IsTrue(hasNoSchema1); + Assert.IsFalse(hasNoSchema2); + } + + [TestMethod()] + public void TestIsEmpty() + { + // arrange + var subject = new DataTable(); + subject.Columns.Add(new DataColumn()); + + // act + var isEmpty1 = subject.IsEmpty(); + subject.Rows.Add(subject.NewRow()); + var isEmpty2 = subject.IsEmpty(); + + // assert + Assert.IsTrue(isEmpty1); + Assert.IsFalse(isEmpty2); + } + + [TestMethod()] + public void TestGetDirectory() + { + // arrange + var assembly = Assembly.GetExecutingAssembly(); + var fileName = Path.GetFileName(assembly.CodeBase); + + // act + var directory = assembly.GetDirectory(); + var codeBase = Path.Combine(directory, fileName); + + // assert + Assert.AreEqual(NormalizePath(codeBase), NormalizePath(assembly.CodeBase)); + } + + [TestMethod()] + public void TestAsMmbrString() + { + // arrange + var subject = new Version(101, 202, 303, 404); + var expected = "101.202.00303.404"; + + // act + var actual = subject.AsMmbrString(); + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod()] + public void TestIsGreaterThan() + { + Assert.IsTrue(2.IsGreaterThan(1)); + Assert.IsFalse(1.IsGreaterThan(2)); + Assert.IsFalse(1.IsGreaterThan(1)); + } + + [TestMethod()] + public void TestIsGreaterThanOrEqualTo() + { + Assert.IsTrue(2.IsGreaterThanOrEqualTo(1)); + Assert.IsFalse(1.IsGreaterThanOrEqualTo(2)); + Assert.IsTrue(1.IsGreaterThanOrEqualTo(1)); + } + + [TestMethod()] + public void TestIsLessThan() + { + Assert.IsTrue(1.IsLessThan(2)); + Assert.IsFalse(2.IsLessThan(1)); + Assert.IsFalse(1.IsLessThan(1)); + } + + [TestMethod()] + public void TestIsLessThanOrEqualTo() + { + Assert.IsTrue(1.IsLessThanOrEqualTo(2)); + Assert.IsFalse(2.IsLessThanOrEqualTo(1)); + Assert.IsTrue(1.IsLessThanOrEqualTo(1)); + } + + [TestMethod()] + public void TestInsertInOrdered() + { + // arrange + var subject = new ObservableCollection<(int, int)>(new[] { (1, 111), (2, 222), (4, 444) }); + var item = (3, 333); + var expected = 2; + + // act + subject.InsertInOrdered(item, t => t.Item1); + var actual = subject.IndexOf(item); + + // assert + Assert.AreEqual(actual, expected); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Base.Test/Extensions/UnitTestLangExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestLangExt.cs new file mode 100644 index 0000000..7df2f62 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestLangExt.cs @@ -0,0 +1,52 @@ +using LanguageExt; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System; +using static LanguageExt.Prelude; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestLangExt + { + [TestMethod] + public void TestAssertSome() + { + Assert.IsTrue(Some(true).AssertSome()); + Assert.ThrowsException(() => Option.None.AssertSome()); + } + + [TestMethod] + public void TestAssertRight() + { + Assert.IsTrue(toEither(true, default(Exception)).AssertRight()); + Assert.ThrowsException(() => toEither(default, () => new Exception()).AssertRight()); + Assert.ThrowsException(() => Either.Bottom.AssertRight()); + } + + [TestMethod] + public void TestLookUp() + { + // arrange + var subject = Array((1, 2), (3, 4), (5, 6)); + var expected1 = Some(4); + var expected2 = None; + + // act + var actual1 = subject.LookUp(3); + var actual2 = subject.LookUp(123); + + // assert + Assert.AreEqual(expected1, actual1); + Assert.AreEqual(expected2, actual2); + } + + [TestMethod] + public void TestContainsNone() + { + Assert.IsTrue(new[] { Some(1), None }.ContainsNone()); + Assert.IsFalse(new[] { Some(1), Some(2) }.ContainsNone()); + Assert.IsFalse(System.Array.Empty>().ContainsNone()); + } + } +} diff --git a/Test/RVis.Base.Test/Extensions/UnitTestNumExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestNumExt.cs new file mode 100644 index 0000000..084ab17 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestNumExt.cs @@ -0,0 +1,99 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using static System.Double; +using static System.Math; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestNumExt + { + [TestMethod] + public void TestToNullable() + { + Assert.IsTrue(NaN.ToNullable() is null); + Assert.IsTrue(1d.ToNullable().HasValue); + } + + [TestMethod] + public void TestFromNullable() + { + Assert.IsTrue(IsNaN(((double?)NaN).FromNullable())); + Assert.IsTrue(IsNaN(default(double?).FromNullable())); + Assert.AreEqual(((double?)1d).FromNullable(), 1d); + } + + [TestMethod] + public void TestGetSignum() + { + Assert.AreEqual(123d.GetSignum(), 1d); + Assert.AreEqual(0d.GetSignum(), 0d); + Assert.AreEqual(-123d.GetSignum(), -1d); + } + + [TestMethod] + public void TestIsFound() + { + Assert.IsTrue(0.IsFound()); + Assert.IsFalse((-1).IsFound()); + } + + [TestMethod] + public void TestIsntFound() + { + Assert.IsTrue((-1).IsntFound()); + Assert.IsFalse(0.IsntFound()); + } + + [TestMethod] + public void TestToSigFigs() + { + // arrange + var subject = 123.456; + var expected = 1.2E2; + + // act + var actual = subject.ToSigFigs(2); + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestIsEqualTo() + { + // arrange + var expected = 2.0; + + // act + var sqrtTwo = Sqrt(expected); + var actual = sqrtTwo * sqrtTwo; + + // assert + Assert.IsTrue(actual.IsEqualTo(expected)); + } + + [TestMethod] + public void TestIsInClosedInterval() + { + Assert.IsTrue(5d.IsInClosedInterval(4d, 6d)); + Assert.IsTrue(5d.IsInClosedInterval(5d, 6d)); + Assert.IsTrue(5d.IsInClosedInterval(4d, 5d)); + Assert.IsFalse(5d.IsInClosedInterval(5d + Constant.TOLERANCE, 6d)); + } + + [TestMethod] + public void TestGetPreviousOrderOfMagnitude() + { + Assert.AreEqual(100d, 123d.GetPreviousOrderOfMagnitude()); + Assert.AreEqual(-100d, -123d.GetPreviousOrderOfMagnitude()); + } + + [TestMethod] + public void TestGetNextOrderOfMagnitude() + { + Assert.AreEqual(1000d, 123d.GetNextOrderOfMagnitude()); + Assert.AreEqual(-1000d, -123d.GetNextOrderOfMagnitude()); + } + } +} diff --git a/Test/RVis.Base.Test/Extensions/UnitTestObjExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestObjExt.cs new file mode 100644 index 0000000..443029e --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestObjExt.cs @@ -0,0 +1,17 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestObjExt + { + [TestMethod] + public void TestResolve() + { + Assert.IsFalse(new object().Resolve(out int _)); + Assert.IsTrue(((object)1).Resolve(out int _)); + Assert.IsFalse(((object)1d).Resolve(out int _)); + } + } +} diff --git a/Test/RVis.Base.Test/Extensions/UnitTestStrExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestStrExt.cs new file mode 100644 index 0000000..a6b13d9 --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestStrExt.cs @@ -0,0 +1,182 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System; +using System.IO; +using System.Linq; +using static System.Environment; +using static System.Math; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestStrExt + { + [TestMethod] + public void TestCheckParseValue() + { + Assert.AreEqual("123".CheckParseValue(), "123"); + Assert.ThrowsException(() => "abc".CheckParseValue()); + } + + [TestMethod] + public void TestIsGreaterThan() + { + Assert.IsTrue("123".IsGreaterThan("0123")); + Assert.IsFalse("0123".IsGreaterThan("123")); + Assert.IsFalse("0123".IsGreaterThan("0123")); + } + + [TestMethod] + public void TestIsGreaterThanOrEqualTo() + { + Assert.IsTrue("123".IsGreaterThanOrEqualTo("0123")); + Assert.IsFalse("0123".IsGreaterThanOrEqualTo("123")); + Assert.IsTrue("0123".IsGreaterThanOrEqualTo("0123")); + } + + [TestMethod] + public void TestIsLessThan() + { + Assert.IsTrue("0123".IsLessThan("123")); + Assert.IsFalse("123".IsLessThan("0123")); + Assert.IsFalse("0123".IsLessThan("0123")); + } + + [TestMethod] + public void TestIsLessThanOrEqualTo() + { + Assert.IsTrue("0123".IsLessThanOrEqualTo("123")); + Assert.IsFalse("123".IsLessThanOrEqualTo("0123")); + Assert.IsTrue("0123".IsLessThanOrEqualTo("0123")); + } + + [TestMethod] + public void TestRejectEmpty() + { + Assert.AreEqual("abc".RejectEmpty(), "abc"); + Assert.IsTrue(" ".RejectEmpty() is null); + } + + [TestMethod] + public void TestToValidFileName() + { + Assert.AreEqual("ab.c".ToValidFileName(), "ab.c"); + Assert.AreEqual(@"a?*\/b.c".ToValidFileName(), "a_b.c"); + } + + [TestMethod] + public void TestPascalToHyphenated() + { + Assert.AreEqual("onetwothree".PascalToHyphenated(), "onetwothree"); + Assert.AreEqual("OneTwoThree".PascalToHyphenated(), "One-Two-Three"); + Assert.AreEqual("One-Two-Three".PascalToHyphenated(), "One--Two--Three"); + } + + [TestMethod] + public void TestToKey() + { + Assert.AreEqual("onetwothree".ToKey(), "onetwothree"); + Assert.AreEqual("one-two-three".ToKey(), "onetwothree"); + } + + [TestMethod] + public void TestElide() + { + Assert.AreEqual("onetwothree".Elide(5), "on..."); + Assert.AreEqual("onetwothree".Elide(11), "onetwothree"); + } + + [TestMethod] + public void TestExpandPath() + { + Assert.AreEqual(@"c:\one\two\three".ExpandPath(), @"c:\one\two\three"); + Assert.IsTrue(@"~/one\two\three".ExpandPath().EndsWith(@"one\two\three")); + Assert.AreEqual(@"~/one\two\three".ExpandPath()[1], ':'); + } + + [TestMethod] + public void TestContractPath() + { + Assert.AreEqual(@"c:\one\two\three".ContractPath(), @"c:\one\two\three"); + Assert.IsTrue(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), @"one\two\three").ContractPath().StartsWith("~/")); + } + + [TestMethod] + public void TestToMD5Hash() + { + Assert.AreEqual("12345678901234567890".ToMD5Hash(), "12345678901234567890".ToMD5Hash()); + Assert.AreNotEqual("12345678901234567890".ToMD5Hash(), "12345678901234567891".ToMD5Hash()); + } + + [TestMethod] + public void TestReplace() + { + Assert.AreEqual("OneTwoThree".Replace('-', 'X'), "OneTwoThree"); + Assert.AreEqual("One-Two-Three".Replace('-', 'X'), "OneXTwoXThree"); + Assert.AreEqual("One--Two--Three".Replace('-', 'X'), "OneXXTwoXXThree"); + } + + [TestMethod] + public void TestEqualsCI() + { + Assert.IsTrue("OneTwoThree".EqualsCI("onetwothree")); + Assert.IsFalse("OneTwoThree".EqualsCI("onetwothreefour")); + } + + [TestMethod] + public void TestDoesNotEqualsCI() + { + Assert.IsFalse("OneTwoThree".DoesNotEqualCI("onetwothree")); + Assert.IsTrue("OneTwoThree".DoesNotEqualCI("onetwothreefour")); + } + + [TestMethod] + public void TestIsAString() + { + Assert.IsFalse(default(string).IsAString()); + Assert.IsFalse("".IsAString()); + Assert.IsFalse(" ".IsAString()); + Assert.IsTrue("a".IsAString()); + } + + [TestMethod] + public void TestIsntAString() + { + Assert.IsTrue(default(string).IsntAString()); + Assert.IsTrue("".IsntAString()); + Assert.IsTrue(" ".IsntAString()); + Assert.IsFalse("a".IsntAString()); + } + + [TestMethod] + public void TestTokenize() + { + // arrange + var toTokenize = "1.23,NA;;abc"; + var expected = new[] { "1.23", "NA", "abc" }; + + // act + var actual = toTokenize.Tokenize(); + + // assert + Assert.IsTrue(expected.SequenceEqual(actual)); + } + + [TestMethod] + public void TestContainsWhiteSpace() + { + Assert.IsFalse(default(string).ContainsWhiteSpace()); + Assert.IsFalse("".ContainsWhiteSpace()); + Assert.IsTrue(" ".ContainsWhiteSpace()); + Assert.IsFalse("a".ContainsWhiteSpace()); + } + + [TestMethod] + public void ToCsvQuoted() + { + Assert.AreEqual("a".ToCsvQuoted(), "a"); + Assert.AreEqual("a ".ToCsvQuoted(), "\"a \""); + Assert.AreEqual(" a".ToCsvQuoted(), "\" a\""); + } + } +} diff --git a/Test/RVis.Base.Test/Extensions/UnitTestTupleExt.cs b/Test/RVis.Base.Test/Extensions/UnitTestTupleExt.cs new file mode 100644 index 0000000..b87209f --- /dev/null +++ b/Test/RVis.Base.Test/Extensions/UnitTestTupleExt.cs @@ -0,0 +1,17 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestTupleExt + { + [TestMethod] + public void TestSnd() + { + Assert.AreEqual(new[] { (1, 111), (2, 222), (3, 333) }.Snd(2), 222); + Assert.ThrowsException(() => new[] { (1, 111), (2, 222), (3, 333) }.Snd(4)); + } + } +} diff --git a/Test/RVis.Base.Test/IO/UnitTestDirectoryOps.cs b/Test/RVis.Base.Test/IO/UnitTestDirectoryOps.cs new file mode 100644 index 0000000..7f91e82 --- /dev/null +++ b/Test/RVis.Base.Test/IO/UnitTestDirectoryOps.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestDirectoryOps + { + [TestMethod] + public void TestApplicationDataDirectory() + { + Assert.IsTrue(DirectoryOps.ApplicationDataDirectory.Exists); + } + + [TestMethod] + public void TestDocumentsDirectory() + { + Assert.IsTrue(DirectoryOps.DocumentsDirectory.Exists); + } + } +} diff --git a/Test/RVis.Base.Test/RVis.Base.Test.csproj b/Test/RVis.Base.Test/RVis.Base.Test.csproj new file mode 100644 index 0000000..12ec627 --- /dev/null +++ b/Test/RVis.Base.Test/RVis.Base.Test.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.1 + + false + + latest + + AnyCPU;x64 + + + + + + + + + + + + + diff --git a/Test/RVis.Base.Test/UniStd/UnitTestCliOpt.cs b/Test/RVis.Base.Test/UniStd/UnitTestCliOpt.cs new file mode 100644 index 0000000..140124c --- /dev/null +++ b/Test/RVis.Base.Test/UniStd/UnitTestCliOpt.cs @@ -0,0 +1,60 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using static LanguageExt.Prelude; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestCliOpt + { + private enum TestOption + { + None, + OptionWithShort, o, + OptionWithoutShort, + OptionArgWithShort, a, + OptionArgWithoutShort + } + + [TestMethod] + public void TestToCliOptSpecs() + { + var cliOptSpecs = Seq( + + $"{TestOption.OptionWithShort}", $"{TestOption.o}", + $"{TestOption.OptionWithoutShort}", + + $"{TestOption.OptionArgWithShort}=", $"{TestOption.a}=", + $"{TestOption.OptionArgWithoutShort}=" + + ).ToCliOptSpecs(); + + Assert.IsTrue(cliOptSpecs.Count == 6); + Assert.AreEqual(cliOptSpecs[0].Option, "option-with-short"); + Assert.IsTrue(cliOptSpecs[0].OptArgType == OptArgType.LongOpt); + Assert.IsTrue(cliOptSpecs[0].OptArgUsage == OptArgUsage.None); + } + + [TestMethod] + public void TestToCliOpts() + { + var cliOptSpecs = Seq( + + $"{TestOption.OptionWithShort}", $"{TestOption.o}", + $"{TestOption.OptionWithoutShort}", + + $"{TestOption.OptionArgWithShort}=", $"{TestOption.a}=", + $"{TestOption.OptionArgWithoutShort}=" + + ).ToCliOptSpecs(); + + var args = new[] { $"--option-arg-without-short=arg" }; + + var cliOpts = args.ToCliOpts(cliOptSpecs); + + var option = cliOpts.GetOpt(TestOption.OptionArgWithoutShort); + + Assert.AreEqual(option.AssertSome().Argument.AssertSome(), "arg"); + } + } +} diff --git a/Test/RVis.Base.Test/UnitTestCheck.cs b/Test/RVis.Base.Test/UnitTestCheck.cs new file mode 100644 index 0000000..f6e07c2 --- /dev/null +++ b/Test/RVis.Base.Test/UnitTestCheck.cs @@ -0,0 +1,21 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestCheck + { + [TestMethod] + public void TestRequireInstanceOf() + { + Assert.ThrowsException(() => Check.RequireInstanceOf((object)"123")); + } + + [TestMethod] + public void TestRequireUniqueElements() + { + Assert.ThrowsException(() => Check.RequireUniqueElements(new[] {(1,111), (2,222), (1, 333) }, t => t.Item1)); + } + } +} diff --git a/Test/RVis.Base.Test/UnitTestMeta.cs b/Test/RVis.Base.Test/UnitTestMeta.cs new file mode 100644 index 0000000..1f14394 --- /dev/null +++ b/Test/RVis.Base.Test/UnitTestMeta.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace RVis.Base.Test +{ + [TestClass] + public class UnitTestMeta + { + [TestMethod] + public void TestProperties() + { + Assert.IsTrue(Meta.FileDate != default); + Assert.IsTrue(Meta.Version != default); + Assert.IsTrue(Meta.VersionMajorDotMinor != default); + Assert.IsTrue(Meta.Product == nameof(RVis)); + Assert.IsTrue(Meta.Company == "HSE"); + } + } +} diff --git a/Test/RVis.Client.Test/Properties/AssemblyInfo.cs b/Test/RVis.Client.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4e69165 --- /dev/null +++ b/Test/RVis.Client.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("RVis.Client.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HSL")] +[assembly: AssemblyProduct("RVis.Client.Test")] +[assembly: AssemblyCopyright("Copyright © HSL 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("321f566b-48cf-4745-9ecf-0262397b8ec5")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Test/RVis.Client.Test/RVis.Client.Test.csproj b/Test/RVis.Client.Test/RVis.Client.Test.csproj new file mode 100644 index 0000000..ea4d121 --- /dev/null +++ b/Test/RVis.Client.Test/RVis.Client.Test.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + {321F566B-48CF-4745-9ECF-0262397B8EC5} + Library + Properties + RVis.Client.Test + RVis.Client.Test + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + latest + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + latest + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + {4dee84dc-ab9e-4ae7-9021-2c0fab41964d} + RVis.Data + + + {3edaa788-c299-4133-b59a-d9f3f13fa978} + RVis.Model + + + {c538987a-1dad-48ca-a3dd-cdd00f75aede} + RVis.Client + + + + + 1.4.0 + + + 1.4.0 + + + + + + \ No newline at end of file diff --git a/Test/RVis.Client.Test/UnitTestSource.cs b/Test/RVis.Client.Test/UnitTestSource.cs new file mode 100644 index 0000000..173979c --- /dev/null +++ b/Test/RVis.Client.Test/UnitTestSource.cs @@ -0,0 +1,47 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Data; +using System; +using System.Linq; + +namespace RVis.Client.Test +{ + [TestClass] + public class UnitTestSource + { +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestEvaluate() + { + // arrange + var invalidR = "seq({i * 10}, {i*30}, by={i*10})"; + var validR = "1:3"; + + string invalidRMessage = default; + NumDataColumn[] validRReturn; + + // act + using (var server = new RVisServer()) + { + using (var client = server.OpenChannel()) + { + // invalid first to show channel is not left faulted + try + { + client.EvaluateNumData(invalidR); + } + catch (Exception ex) + { + invalidRMessage = ex.Message; + } + + validRReturn = client.EvaluateNumData(validR); + } + } + + // assert + Assert.IsTrue(invalidRMessage.Contains("'i' not found")); + Assert.IsTrue(new[] { 1.0, 2, 3 }.SequenceEqual(validRReturn[0].Data.ToArray())); + } +#endif + } +} diff --git a/Test/RVis.Data.Test/RVis.Data.Test.csproj b/Test/RVis.Data.Test/RVis.Data.Test.csproj new file mode 100644 index 0000000..3a75176 --- /dev/null +++ b/Test/RVis.Data.Test/RVis.Data.Test.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.1 + + false + + AnyCPU;x64 + + + + + + + + + + + + + diff --git a/Test/RVis.Data.Test/UnitTestTable.cs b/Test/RVis.Data.Test/UnitTestTable.cs new file mode 100644 index 0000000..e25bf32 --- /dev/null +++ b/Test/RVis.Data.Test/UnitTestTable.cs @@ -0,0 +1,33 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ProtoBuf; +using System.IO; +using System.Linq; + +namespace RVis.Data.Test +{ + [TestClass] + public class UnitTestTable + { + [TestMethod] + public void TestSerializeDataTable() + { + // arrange + var columns = new[] { + new NumDataColumn("doubles", new[] { 1.0, 2.0, 3.0 }), + new NumDataColumn("more doubles", new[] { 4.0, 5.0, 6.0 }) + }; + var toSerialize = new NumDataTable("test", columns); + + // act + var memoryStream = new MemoryStream(); + Serializer.Serialize(memoryStream, toSerialize); + memoryStream.Position = 0; + var deserialized = Serializer.Deserialize(memoryStream); + + // assert + Assert.AreEqual(toSerialize.Name, deserialized.Name); + Assert.IsTrue(toSerialize[0].Data.SequenceEqual(deserialized[0].Data)); + Assert.IsTrue(toSerialize[1].Data.SequenceEqual(deserialized[1].Data)); + } + } +} diff --git a/Test/RVis.Model.Test/Code/UnitTestElementCandidate.cs b/Test/RVis.Model.Test/Code/UnitTestElementCandidate.cs new file mode 100644 index 0000000..70aa7ec --- /dev/null +++ b/Test/RVis.Model.Test/Code/UnitTestElementCandidate.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestElementCandidate + { + //[TestMethod()] + //public void TestElementCandidate() + //{ + // Assert.Fail(); + //} + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Code/UnitTestManagedImport.cs b/Test/RVis.Model.Test/Code/UnitTestManagedImport.cs new file mode 100644 index 0000000..d2afc98 --- /dev/null +++ b/Test/RVis.Model.Test/Code/UnitTestManagedImport.cs @@ -0,0 +1,94 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Client; +using RVis.Test; +using System.IO; +using System.Linq; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestManagedImport + { + //[TestMethod()] + //public void TestManagedImport() + //{ + // Assert.Fail(); + //} + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestInspectAsync() + { + // arrange + var pathToLibrary = TestData.SimLibraryDirectory.FullName; + var simLibrary = new SimLibrary(); + simLibrary.LoadFrom(pathToLibrary); + var pathToCode = Path.Combine(pathToLibrary, "InspectTmpl", "inspect.R"); + + // act + using (var managedImport = new ManagedImport(pathToCode, simLibrary)) + { + using (var server = new RVisServer()) + { + using (var client = server.OpenChannel()) + { + managedImport.InspectAsync(client).Wait(); + } + } + + // assert + Assert.AreEqual(managedImport.Scalars.Count, 2); + Assert.AreEqual(managedImport.ParameterCandidates.Count, 2); + Assert.AreEqual(managedImport.DataSets.Count, 3); + Assert.AreEqual(managedImport.ValueCandidates.Count, 3); + Assert.AreEqual(managedImport.ValueCandidates.Single(vc => vc.Name == "o").ElementCandidates.Count, 3); + } + } +#endif + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestSetExecutorAsync() + { + // arrange + var pathToLibrary = TestData.SimLibraryDirectory.FullName; + var simLibrary = new SimLibrary(); + simLibrary.LoadFrom(pathToLibrary); + var pathToCode = Path.Combine(pathToLibrary, "InspectExec", "inspect.R"); + + // act + using (var managedImport = new ManagedImport(pathToCode, simLibrary)) + { + using (var server = new RVisServer()) + { + using (var client = server.OpenChannel()) + { + managedImport.InspectAsync(client).Wait(); + managedImport.SetExecutorAsync(managedImport.UnaryFunctions[0], managedImport.ScalarSets[0], client).Wait(); + } + } + + // assert + Assert.AreEqual(managedImport.ExecutorOutput.NColumns, 3); + Assert.AreEqual(managedImport.ExecutorParameterCandidates.Count, 2); + Assert.AreEqual(managedImport.ExecutorValueCandidates.Count, 2); + Assert.AreEqual(managedImport.ScalarSets.Count, 1); + Assert.AreEqual(managedImport.UnaryFunctions.Count, 1); + Assert.AreEqual(managedImport.ValueCandidates.Count, 1); + } + } +#endif + + //[TestMethod()] + //public void TestImportExecToLibrary() + //{ + // Assert.Fail(); + //} + + //[TestMethod()] + //public void TestImportTmplToLibrary() + //{ + // Assert.Fail(); + //} + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Code/UnitTestParameterCandidate.cs b/Test/RVis.Model.Test/Code/UnitTestParameterCandidate.cs new file mode 100644 index 0000000..b1a75af --- /dev/null +++ b/Test/RVis.Model.Test/Code/UnitTestParameterCandidate.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestParameterCandidate + { + //[TestMethod()] + //public void TestCreateForExec() + //{ + // Assert.Fail(); + //} + + //[TestMethod()] + //public void TestParameterCandidate() + //{ + // Assert.Fail(); + //} + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Code/UnitTestValueCandidate.cs b/Test/RVis.Model.Test/Code/UnitTestValueCandidate.cs new file mode 100644 index 0000000..79bb87b --- /dev/null +++ b/Test/RVis.Model.Test/Code/UnitTestValueCandidate.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestValueCandidate + { + //[TestMethod()] + //public void TestCreateForExec() + //{ + // Assert.Fail(); + //} + + //[TestMethod()] + //public void TestValueCandidate() + //{ + // Assert.Fail(); + //} + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/DistributionImpl.cs b/Test/RVis.Model.Test/Distribution/DistributionImpl.cs new file mode 100644 index 0000000..5aa18c8 --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/DistributionImpl.cs @@ -0,0 +1,19 @@ +using RVis.Client; +using RVis.Data; + +namespace RVis.Model.Test +{ + internal static class DistributionImpl + { + internal static NumDataColumn[] GetNumData(string code) + { + using (var server = new RVisServer()) + { + using (var client = server.OpenChannel()) + { + return client.EvaluateNumData(code); + } + } + } + } +} diff --git a/Test/RVis.Model.Test/Distribution/UnitTestBetaDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestBetaDistribution.cs new file mode 100644 index 0000000..63b1436 --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestBetaDistribution.cs @@ -0,0 +1,92 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestBetaDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestBetaCumulativeDistributionAtBounds() + { + // arrange + var subject = new BetaDistribution(0.5, 0.5, 0.3, 0.7); + var expectedLowerP = GetNumData("pbeta(0.3, 0.5, 0.5)")[0].Data[0]; + var expectedUpperP = GetNumData("pbeta(0.7, 0.5, 0.5)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestBetaWithBoundsFillSamples() + { + // arrange + var subject = new BetaDistribution(0.5, 0.5, 0.3, 0.7); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.3 && d < 0.7)); + } + + [TestMethod()] + public void TestBetaWithBoundsGetSample() + { + // arrange + var subject = new BetaDistribution(0.5, 0.5, 0.3, 0.7); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.3 && sample < 0.7); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestBetaGetDensities() + { + // arrange + var subject = new BetaDistribution(0.5, 0.5); + var expected = GetNumData( + "sapply(seq(qbeta(0.3, 0.5, 0.5), qbeta(0.7, 0.5, 0.5), length.out = 5), function(cd){dbeta(cd, 0.5, 0.5)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestBetaRoundTrip() + { + // arrange + var expected = new BetaDistribution(0.5, 0.5, 0.3, 0.7); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestBetaScaledDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestBetaScaledDistribution.cs new file mode 100644 index 0000000..5035cd4 --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestBetaScaledDistribution.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestBetaScaledDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestBetaScaledCumulativeDistributionAtBounds() + { + // arrange + var subject = new BetaScaledDistribution(0.5, 0.5, 0d, 1d, 0.3, 0.7); + + var expectedLowerP = GetNumData("pbeta(0.3, 0.5, 0.5)")[0].Data[0]; + var expectedUpperP = GetNumData("pbeta(0.7, 0.5, 0.5)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestScaledBetaWithBoundsFillSamples() + { + // arrange + var subject = new BetaScaledDistribution(0.5, 0.5, 0d, 1d, 0.3, 0.7); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.3 && d < 0.7)); + } + + [TestMethod()] + public void TestScaledBetaWithBoundsGetSample() + { + // arrange + var subject = new BetaScaledDistribution(0.5, 0.5, 0d, 1d, 0.3, 0.7); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.3 && sample < 0.7); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestBetaScaledGetDensities() + { + // arrange + var subject = new BetaScaledDistribution(0.5, 0.5, 0d, 1d); + var expected = GetNumData( + "sapply(seq(qbeta(0.3, 0.5, 0.5), qbeta(0.7, 0.5, 0.5), length.out = 5), function(cd){dbeta(cd, 0.5, 0.5)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestScaledBetaRoundTrip() + { + // arrange + var expected = new BetaScaledDistribution(0.5, 0.5, 0d, 1d, 0.3, 0.7); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestDistribution.cs new file mode 100644 index 0000000..8128743 --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestDistribution.cs @@ -0,0 +1,85 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Math; +using static System.Convert; +using RVis.Base.Extensions; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestDistribution + { + [TestMethod()] + public void TestGetDistributionTypes() + { + // arrange + var expected = ToInt32(Log((int)DistributionType.All + 1) / Log(2d)); + + // act + var actual = Distribution.GetDistributionTypes(DistributionType.All).Count; + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod()] + public void TestSerializeDistributions() + { + // arrange + var instances = Distribution.GetDefaults(); + + // act + var serialized = Distribution.SerializeDistributions(instances); + + // assert + Assert.IsTrue(serialized.Length == instances.Count); + } + + [TestMethod()] + public void TestDeserializeDistributions() + { + // arrange + var instances = Distribution.GetDefaults(); + var serialized = Distribution.SerializeDistributions(instances); + + // act + var deserialized = Distribution.DeserializeDistributions(serialized); + + // assert + Assert.IsTrue(instances.SequenceEqual(deserialized)); + } + + [TestMethod()] + public void TestGetDefault() + { + // arrange + var expected = NormalDistribution.Default; + + // act + var actual = Distribution.GetDefault(DistributionType.Normal); + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod()] + public void GetDefaultsTest() + { + // arrange + var expectedCount = Distribution.GetDistributionTypes(DistributionType.All).Count; + + // act + var defaults = Distribution.GetDefaults(); + var actualCount = defaults.Count; + + // assert + Assert.AreEqual(expectedCount, actualCount); + Assert.IsTrue(defaults.AllUnique(d => d.GetType())); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestGammaDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestGammaDistribution.cs new file mode 100644 index 0000000..80c4f0c --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestGammaDistribution.cs @@ -0,0 +1,92 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestGammaDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestGammaCumulativeDistributionAtBounds() + { + // arrange + var subject = new GammaDistribution(1d, 1d, 0.3, 0.7); + var expectedLowerP = GetNumData("pgamma(0.3, 1.0)")[0].Data[0]; + var expectedUpperP = GetNumData("pgamma(0.7, 1.0)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestGammaWithBoundsFillSamples() + { + // arrange + var subject = new GammaDistribution(1d, 1d, 0.3, 0.7); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.3 && d < 0.7)); + } + + [TestMethod()] + public void TestGammaWithBoundsGetSample() + { + // arrange + var subject = new GammaDistribution(1d, 1d, 0.3, 0.7); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.3 && sample < 0.7); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestGammaGetDensities() + { + // arrange + var subject = new GammaDistribution(1d, 1d); + var expected = GetNumData( + "sapply(seq(qgamma(0.3, 1.0), qgamma(0.7, 1.0), length.out = 5), function(cd){dgamma(cd, 1.0)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestGammaRoundTrip() + { + // arrange + var expected = new GammaDistribution(1d, 1d, 0.3, 0.7); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestLogNormalDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestLogNormalDistribution.cs new file mode 100644 index 0000000..5ab242a --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestLogNormalDistribution.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; +using static System.Math; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestLogNormalDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestLogNormalCumulativeDistributionAtBounds() + { + // arrange + var subject = new LogNormalDistribution(0d, 1d, Log(0.3), Log(0.7)); + var expectedLowerP = GetNumData("plnorm(0.3, 0.0, 1.0)")[0].Data[0]; + var expectedUpperP = GetNumData("plnorm(0.7, 0.0, 1.0)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestLogNormalWithBoundsFillSamples() + { + // arrange + var subject = new LogNormalDistribution(0d, 1d, Log(0.3), Log(0.7)); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.3 && d < 0.7)); + } + + [TestMethod()] + public void TestLogNormalWithBoundsGetSample() + { + // arrange + var subject = new LogNormalDistribution(0d, 1d, Log(0.3), Log(0.7)); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.3 && sample < 0.7); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestLogNormalGetDensities() + { + // arrange + var subject = new LogNormalDistribution(0d, 1d); + var expected = GetNumData( + "sapply(seq(qlnorm(0.3, 0.0, 1.0), qlnorm(0.7, 0.0, 1.0), length.out = 5), function(cd){dlnorm(cd, 0.0, 1.0)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestLogNormalRoundTrip() + { + // arrange + var expected = new LogNormalDistribution(0d, 1d, 0.3, 0.7); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestLogUniformDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestLogUniformDistribution.cs new file mode 100644 index 0000000..1e9585c --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestLogUniformDistribution.cs @@ -0,0 +1,94 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; +using static System.Math; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestLogUniformDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestLogUniformCumulativeDistributionAtBounds() + { + // arrange + var subject = new LogUniformDistribution(Log(0.1), Log(0.9)); + var expectedLowerP = GetNumData("punif(0.1, 0.1, 0.9)")[0].Data[0]; + var expectedUpperP = GetNumData("punif(0.9, 0.1, 0.9)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestLogUniformWithBoundsFillSamples() + { + // arrange + var subject = new LogUniformDistribution(Log(0.1), Log(0.9)); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.1 && d < 0.9)); + } + + [TestMethod()] + public void TestLogUniformWithBoundsGetSample() + { + // arrange + var subject = new LogUniformDistribution(Log(0.1), Log(0.9)); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.1 && sample < 0.9); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestLogUniformGetDensities() + { + // arrange + var subject = new LogUniformDistribution(Log(0.1), Log(0.9)); + var expected = GetNumData( + "sapply(seq(qunif(0.3, log(0.1), log(0.9)), qunif(0.7, log(0.1), log(0.9)), length.out = 5), function(cd){dunif(cd, log(0.1), log(0.9))})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestLogUniformRoundTrip() + { + // arrange + var expected = new LogUniformDistribution(Log(0.1), Log(0.9)); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected.Lower, actual.Lower, Base.Constant.TOLERANCE); + Assert.AreEqual(expected.Upper, actual.Upper, Base.Constant.TOLERANCE); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestNormalDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestNormalDistribution.cs new file mode 100644 index 0000000..d3d384a --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestNormalDistribution.cs @@ -0,0 +1,92 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestNormalDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestNormalCumulativeDistributionAtBounds() + { + // arrange + var subject = new NormalDistribution(0.5, 1d, 0.3, 0.7); + var expectedLowerP = GetNumData("pnorm(0.3, 0.5, 1.0)")[0].Data[0]; + var expectedUpperP = GetNumData("pnorm(0.7, 0.5, 1.0)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestNormalWithBoundsFillSamples() + { + // arrange + var subject = new NormalDistribution(0.5, 1d, 0.3, 0.7); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.3 && d < 0.7)); + } + + [TestMethod()] + public void TestNormalWithBoundsGetSample() + { + // arrange + var subject = new NormalDistribution(0.5, 1d, 0.3, 0.7); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.3 && sample < 0.7); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestNormalGetDensities() + { + // arrange + var subject = new NormalDistribution(0.5, 1d); + var expected = GetNumData( + "sapply(seq(qnorm(0.3, 0.5, 1.0), qnorm(0.7, 0.5, 1.0), length.out = 5), function(cd){dnorm(cd, 0.5, 1.0)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestNormalRoundTrip() + { + // arrange + var expected = new NormalDistribution(0.5, 1d, 0.3, 0.7); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestStudentTDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestStudentTDistribution.cs new file mode 100644 index 0000000..92dcb8c --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestStudentTDistribution.cs @@ -0,0 +1,92 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestStudentTDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestStudentTCumulativeDistributionAtBounds() + { + // arrange + var subject = new StudentTDistribution(0d, 1d, 2d, 0.3, 0.7); + var expectedLowerP = GetNumData("pt(0.3, 2)")[0].Data[0]; + var expectedUpperP = GetNumData("pt(0.7, 2)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestStudentTWithBoundsFillSamples() + { + // arrange + var subject = new StudentTDistribution(0d, 1d, 2d, 0.3, 0.7); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.3 && d < 0.7)); + } + + [TestMethod()] + public void TestStudentTWithBoundsGetSample() + { + // arrange + var subject = new StudentTDistribution(0d, 1d, 2d, 0.3, 0.7); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.3 && sample < 0.7); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestStudentTGetDensities() + { + // arrange + var subject = new StudentTDistribution(0d, 1d, 2d); + var expected = GetNumData( + "sapply(seq(qt(0.3, 2), qt(0.7, 2), length.out = 5), function(cd){dt(cd, 2)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestStudentTRoundTrip() + { + // arrange + var expected = new StudentTDistribution(0d, 1d, 2d, 0.3, 0.7); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Distribution/UnitTestUniformDistribution.cs b/Test/RVis.Model.Test/Distribution/UnitTestUniformDistribution.cs new file mode 100644 index 0000000..1ff370e --- /dev/null +++ b/Test/RVis.Model.Test/Distribution/UnitTestUniformDistribution.cs @@ -0,0 +1,92 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using static LanguageExt.Prelude; +using static RVis.Model.Test.DistributionImpl; +using static System.Double; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestUniformDistribution + { +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestUniformCumulativeDistributionAtBounds() + { + // arrange + var subject = new UniformDistribution(0.1, 0.9); + var expectedLowerP = GetNumData("punif(0.1, 0.1, 0.9)")[0].Data[0]; + var expectedUpperP = GetNumData("punif(0.9, 0.1, 0.9)")[0].Data[0]; + + // act + var (actualLowerP, actualUpperP) = subject.CumulativeDistributionAtBounds; + + // assert + Assert.AreEqual(expectedLowerP, actualLowerP, Base.Constant.TOLERANCE); + Assert.AreEqual(expectedUpperP, actualUpperP, Base.Constant.TOLERANCE); + } +#endif + + [TestMethod()] + public void TestUniformWithBoundsFillSamples() + { + // arrange + var subject = new UniformDistribution(0.1, 0.9); + var samples = Range(0, 42).Map(_ => NaN).ToArray(); + + // act + subject.FillSamples(samples); + + // assert + Assert.IsTrue(samples.All(d => d > 0.1 && d < 0.9)); + } + + [TestMethod()] + public void TestUniformWithBoundsGetSample() + { + // arrange + var subject = new UniformDistribution(0.1, 0.9); + + // act + var sample = subject.GetSample(); + + // assert + Assert.IsTrue(sample > 0.1 && sample < 0.9); + } + +#if !IS_PIPELINES_BUILD + [TestMethod()] + public void TestUniformGetDensities() + { + // arrange + var subject = new UniformDistribution(0.1, 0.9); + var expected = GetNumData( + "sapply(seq(qunif(0.3, 0.1, 0.9), qunif(0.7, 0.1, 0.9), length.out = 5), function(cd){dunif(cd, 0.1, 0.9)})" + )[0].Data.ToArr(); + + // act + var (_, actual) = subject.GetDensities(0.3, 0.7, 5); + + // assert + Assert.IsTrue(actual.Count == 5); + expected.Iter((i, d) => Assert.AreEqual(d, actual[i], Base.Constant.TOLERANCE)); + } +#endif + + [TestMethod()] + public void TestUniformRoundTrip() + { + // arrange + var expected = new UniformDistribution(0.1, 0.9); + + // act + var serialized = expected.ToString(); + var deserialized = Distribution.DeserializeDistribution(serialized); + var distribution = deserialized.IfNone(() => { Assert.Fail(); return default; }); + + // assert + var actual = Base.Check.RequireInstanceOf(distribution); + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Extensions/UnitTestDistributionExt.cs b/Test/RVis.Model.Test/Extensions/UnitTestDistributionExt.cs new file mode 100644 index 0000000..e3f90e0 --- /dev/null +++ b/Test/RVis.Model.Test/Extensions/UnitTestDistributionExt.cs @@ -0,0 +1,38 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using RVis.Model.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static LanguageExt.Prelude; + +namespace RVis.Model.Test +{ + [TestClass()] + public class TestDistributionExt + { + [TestMethod()] + public void TestToStringIfSome() + { + Assert.IsTrue(Some(Distribution.GetDefault(DistributionType.Normal)).ToStringIfSome("x").IsAString()); + Assert.IsNull(LangExt.NoneOf().ToStringIfSome("x")); + } + + [TestMethod()] + public void TestSetDistribution() + { + // arrange + var distributions = Distribution.GetDefaults(); + var toSet = new NormalDistribution(0d, 1d); + + // act + distributions = distributions.SetDistribution(toSet); + + // assert + Assert.IsTrue(distributions.Count(d => d.DistributionType == DistributionType.Normal) == 1); + Assert.AreEqual(distributions.Single(d => d.DistributionType == DistributionType.Normal), toSet); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Extensions/UnitTestSimEvidenceExt.cs b/Test/RVis.Model.Test/Extensions/UnitTestSimEvidenceExt.cs new file mode 100644 index 0000000..8bb2ca7 --- /dev/null +++ b/Test/RVis.Model.Test/Extensions/UnitTestSimEvidenceExt.cs @@ -0,0 +1,89 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using RVis.Model.Extensions; +using System.Linq; +using static LanguageExt.Prelude; + +namespace RVis.Model.Test +{ + [TestClass()] + public class UnitTestSimEvidenceExt + { + [TestMethod()] + public void TestSimEvidenceSourceReferenceRoundTrip() + { + // arrange + var evidenceSources = Range(1, 5, 2).Map(id => + new SimEvidenceSource( + id, + $"name{id:0000}", + $"desc{id:0000}", + toSet(Range(id, id).Map(s => $"subject{s:0000}")), + $"refName{id:0000}", + $"refHash{id:0000}" + ) + ) + .ToArr(); + + var mockEvidence = new Mock(); + mockEvidence.Setup(me => me.EvidenceSources).Returns(evidenceSources); + + SimEvidenceSource expected = evidenceSources.Skip(1).Take(1).Single(); + ISimEvidence evidence = mockEvidence.Object; + + // act + var reference = expected.GetReference(); + var actual = evidence.GetEvidenceSource(reference); + + // assert + Assert.AreEqual(expected, actual); + } + + [TestMethod()] + public void TestSimObservationsReferenceRoundTrip() + { + // arrange + var evidenceSources = Range(1, 5, 2).Map(id => + new SimEvidenceSource( + 100 + id, + $"name{id:0000}", + $"desc{id:0000}", + toSet(Range(id, id).Map(s => $"subject{s:0000}")), + $"refName{id:0000}", + $"refHash{id:0000}" + ) + ) + .ToArr(); + + var observations = Range(1, 5, 2) + .Map( + id => new SimObservations( + 10 + id, + 100 + id, + $"subjectX", + $"refName{id:0000}", + Array(1d, 2, 3), + Array(4d, 5, 6) + ) + ) + .ToArr(); + + var observationsSet = new SimObservationsSet(observations.Head().Subject, observations); + + var mockEvidence = new Mock(); + mockEvidence.Setup(me => me.EvidenceSources).Returns(evidenceSources); + mockEvidence.Setup(me => me.GetObservationSet(It.Is(s => s == "subjectX"))).Returns(observationsSet); + ISimEvidence evidence = mockEvidence.Object; + + SimEvidenceSource evidenceSource = evidenceSources.Skip(1).Take(1).Single(); + SimObservations expected = observations.Skip(1).Take(1).Single(); + + // act + var reference = evidence.GetReference(expected); + var actual = evidence.GetObservations(reference); + + // assert + Assert.AreEqual(expected, actual); + } + } +} \ No newline at end of file diff --git a/Test/RVis.Model.Test/Properties/AssemblyInfo.cs b/Test/RVis.Model.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..18f15d2 --- /dev/null +++ b/Test/RVis.Model.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("RVis.Model.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HSL")] +[assembly: AssemblyProduct("RVis.Model.Test")] +[assembly: AssemblyCopyright("Copyright © HSL 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("7c5f15ac-34dc-42b0-a73c-4854b8f95d97")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Test/RVis.Model.Test/R/UnitTestR.cs b/Test/RVis.Model.Test/R/UnitTestR.cs new file mode 100644 index 0000000..afe1240 --- /dev/null +++ b/Test/RVis.Model.Test/R/UnitTestR.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using RVis.Client; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RVis.Model.Test +{ + [TestClass] + public class UnitTestR + { + [TestInitialize] + public void TestInitialize() + { + serverPool = new RVisServerPool(); + serverPool.CreatePool(() => new RVisServer(), 2); + } + + [TestCleanup] + public void TestCleanup() + { + serverPool.DestroyPool(); + } + + private RVisServerPool serverPool; + +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestServerPoolRenewServerLicense() + { + // arrange + var maybeServerLicense = serverPool.RequestServer(); + var serverLicense = maybeServerLicense.AssertSome(); + var expected = DateTime.Now.Ticks.ToString("X"); + using (serverLicense) + { + var client = serverLicense.Client; + client.Clear(); + client.EvaluateNonQuery($"expected <- \"{expected}\""); + } + + // act + var maybeRenewedServerLicense = serverPool.RenewServerLicense(serverLicense); + Assert.IsTrue(maybeRenewedServerLicense.IsSome); + + Dictionary dictionary; + using (var renewedServerLicense = maybeRenewedServerLicense.AssertSome()) + { + var client = renewedServerLicense.Client; + dictionary = client.EvaluateStrings("expected"); + } + + // assert + Assert.IsNotNull(dictionary); + Assert.AreEqual(dictionary.Count, 1); + + var strings = dictionary.Values.First(); + Assert.AreEqual(strings?.Length, 1); + + var actual = strings[0] as string; + Assert.AreEqual(actual, expected); + } +#endif + } +} diff --git a/Test/RVis.Model.Test/RVis.Model.Test.csproj b/Test/RVis.Model.Test/RVis.Model.Test.csproj new file mode 100644 index 0000000..c3da347 --- /dev/null +++ b/Test/RVis.Model.Test/RVis.Model.Test.csproj @@ -0,0 +1,122 @@ + + + + Debug + AnyCPU + {7C5F15AC-34DC-42B0-A73C-4854B8F95D97} + Library + Properties + RVis.Model.Test + RVis.Model.Test + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + latest + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + latest + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {75df8b2f-d179-436d-8107-be640f3c3531} + RVis.Base + + + {4dee84dc-ab9e-4ae7-9021-2c0fab41964d} + RVis.Data + + + {3edaa788-c299-4133-b59a-d9f3f13fa978} + RVis.Model + + + {c538987a-1dad-48ca-a3dd-cdd00f75aede} + RVis.Client + + + + + 1.4.0 + + + 1.4.0 + + + 0.13.0 + + + + + + \ No newline at end of file diff --git a/Test/RVis.Model.Test/Sim/UnitTestSimConfig.cs b/Test/RVis.Model.Test/Sim/UnitTestSimConfig.cs new file mode 100644 index 0000000..cc74b72 --- /dev/null +++ b/Test/RVis.Model.Test/Sim/UnitTestSimConfig.cs @@ -0,0 +1,34 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Nett; +using ProtoBuf; +using RVis.Test; +using System.IO; +using static LanguageExt.Prelude; +using static RVis.Model.Sim; + +namespace RVis.Model.Test +{ + [TestClass] + public class UnitTestSimConfig + { + [TestMethod] + public void TestSerialize() + { + // arrange + var memoryStream = new MemoryStream(); + var pathToSimLibrary = TestData.SimLibraryDirectory.FullName; + var pathToConfig = Path.Combine(pathToSimLibrary, "CubicExec", ".rvis", "config.toml"); + var config = Sim.ReadConfigFromFile(pathToConfig); + var expected = Toml.WriteString(config); + + // act + Serializer.Serialize(memoryStream, FromToml(config)); + memoryStream.Position = 0; + var deserialized = Serializer.Deserialize(memoryStream); + var actual = Toml.WriteString(ToToml(deserialized)); + + // assert + Assert.IsTrue(actual == expected); + } + } +} diff --git a/Test/RVis.Model.Test/Sim/UnitTestSimInput.cs b/Test/RVis.Model.Test/Sim/UnitTestSimInput.cs new file mode 100644 index 0000000..6974e36 --- /dev/null +++ b/Test/RVis.Model.Test/Sim/UnitTestSimInput.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static LanguageExt.Prelude; + +namespace RVis.Model.Test +{ + [TestClass] + public class UnitTestSimInput + { + [TestMethod] + public void TestEquality() + { + // arrange + var input1 = new SimInput(Array( + new SimParameter("n1", "v1", default, default), + new SimParameter("n2", "v2", default, default) + ), isDefault: false); + + var input2 = new SimInput(Array( + new SimParameter("n1", "v1", default, default), + new SimParameter("n2", "v2", default, default) + ), isDefault: false); + + // act + var areEqual = input1 == input2; + + // assert + Assert.IsTrue(areEqual); + } + } +} diff --git a/Test/RVis.Model.Test/SimEvidence/UnitTestSimObservationsSet.cs b/Test/RVis.Model.Test/SimEvidence/UnitTestSimObservationsSet.cs new file mode 100644 index 0000000..ca3bfa6 --- /dev/null +++ b/Test/RVis.Model.Test/SimEvidence/UnitTestSimObservationsSet.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static LanguageExt.Prelude; + +namespace RVis.Model.Test +{ + [TestClass] + public class UnitTestSimObservationsSet + { + [TestMethod] + public void TestAddRemoveObservations() + { + // arrange + var simObservations = Range(1, 5, 2) + .Map( + id => new SimObservations( + 10 + id, + 100 + id, + $"subjectX", + $"refName{id:0000}", + Array(1d, 2, 3), + Array(4d, 5, 6) + ) + ) + .ToArr(); + var subject = new SimObservationsSet(simObservations.Head().Subject, simObservations); + + // act + var (withAddition, added) = SimObservationsSet.AddObservations(subject, 100 + 2, $"refName{2:0000}", Array(7d, 8, 9), Array(10d, 11, 12)); + var withRemoval = SimObservationsSet.RemoveObservations(withAddition, added); + var withAllRemoved = SimObservationsSet.RemoveAllObservations(withAddition, 100 + 2); + + // assert + Assert.IsTrue(withAddition.Observations.Count == 6); + Assert.IsTrue(added.ID == 10 + (5 * 2 - 1) + 1); + Assert.IsTrue(withAddition.Observations.Exists(o => o.RefName == $"refName{2:0000}")); + Assert.AreEqual(subject, withRemoval); + Assert.AreEqual(subject, withAllRemoved); + Assert.IsFalse(subject.Equals(withAddition)); + } + } +} diff --git a/Test/RVis.ROps.Test/Properties/AssemblyInfo.cs b/Test/RVis.ROps.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d65b1de --- /dev/null +++ b/Test/RVis.ROps.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("RVis.ROps.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HSL")] +[assembly: AssemblyProduct("RVis.ROps.Test")] +[assembly: AssemblyCopyright("Copyright © HSL 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("dc958d41-14f0-4c4d-a5ca-430ddc42fa50")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Test/RVis.ROps.Test/RVis.ROps.Test.csproj b/Test/RVis.ROps.Test/RVis.ROps.Test.csproj new file mode 100644 index 0000000..42cfbef --- /dev/null +++ b/Test/RVis.ROps.Test/RVis.ROps.Test.csproj @@ -0,0 +1,95 @@ + + + + Debug + AnyCPU + {DC958D41-14F0-4C4D-A5CA-430DDC42FA50} + Library + Properties + RVis.ROps.Test + RVis.ROps.Test + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + + {75df8b2f-d179-436d-8107-be640f3c3531} + RVis.Base + + + {4dee84dc-ab9e-4ae7-9021-2c0fab41964d} + RVis.Data + + + {3edaa788-c299-4133-b59a-d9f3f13fa978} + RVis.Model + + + {a43377d5-7f30-4118-b1dd-2ddb57cbd399} + RVis.ROps + + + + + 1.4.0 + + + 1.4.0 + + + + + + \ No newline at end of file diff --git a/Test/RVis.ROps.Test/UnitTestCode.cs b/Test/RVis.ROps.Test/UnitTestCode.cs new file mode 100644 index 0000000..34986bf --- /dev/null +++ b/Test/RVis.ROps.Test/UnitTestCode.cs @@ -0,0 +1,167 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Test; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace RVis.ROps.Test +{ + [TestClass] + public class UnitTestCode + { +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestSourceLines() + { + // arrange + var lines = new[] + { + "a <- 1", + "if(a == 1) {", + "a <- 2", + "}" + }; + + // act + ROpsApi.SourceLines(lines); + var @out = ROpsApi.EvaluateNumData("a"); + + // assert + Assert.AreEqual(@out.First().Data[0], 2.0); + } +#endif + +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestEvaluate() + { + // arrange + var expr01 = @"list(x = ""abc"", y = ""def"")"; + var expected01 = new Dictionary { ["x"] = new[] { "abc" }, ["y"] = new[] { "def" } }; + + // act + var actual01 = ROpsApi.Evaluate(expr01); + + // assert + Assert.AreEqual(expected01.Count, actual01.Count); + + var areEqual = expected01.Keys.All( + k => actual01.ContainsKey(k) && actual01[k][0] as string == expected01[k][0] as string + ); + Assert.IsTrue(areEqual); + } +#endif + +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestEvaluateNumData() + { + // arrange + var expr01 = "2+2"; + var expected01 = new[] { 4.0 }; + + var expr02 = "list(x = 1:4, y = 5:8)"; + var x = Enumerable.Range(1, 4).Select(i => i * 1.0).ToArray(); + var y = Enumerable.Range(5, 4).Select(i => i * 1.0).ToArray(); + + var expr03 = "seq(0,1,by=0.1)"; + var expected03 = Enumerable.Range(0, 11).Select(i => i * 0.1); + + // act + var actual01 = ROpsApi.EvaluateNumData(expr01); + var actual02 = ROpsApi.EvaluateNumData(expr02); + var actual03 = ROpsApi.EvaluateNumData(expr03); + + // assert + Assert.IsTrue(actual01.Length == 1); + Assert.IsTrue(expected01.SequenceEqual(actual01[0].Data)); + + Assert.IsTrue(actual02[0].Name == nameof(x) && x.SequenceEqual(actual02[0].Data)); + Assert.IsTrue(actual02[1].Name == nameof(y) && y.SequenceEqual(actual02[1].Data)); + + Assert.IsTrue(expected03.SequenceEqual(actual03[0].Data)); + } +#endif + +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestInspectExecSymbols() + { + // arrange + var memoryStream = new MemoryStream(); + var pathToSimLibrary = TestData.SimLibraryDirectory.FullName; + var pathToCode = Path.Combine(pathToSimLibrary, "InspectExec", "inspect.R"); + const string execSymbol = "run"; + const string parametersSymbol = "parameters"; + const string p1Symbol = "p1"; + //const string p2Symbol = "p2"; + const string outputSymbol = "o"; + //const string o1Symbol = "o1"; + const string o2Symbol = "o2"; + const string o3Symbol = "o3"; + const string o3LocalSymbol = "Ro3"; + + // act + var symbolInfos = ROpsApi.InspectSymbols(pathToCode); + var execSI = symbolInfos.SingleOrDefault(si => si.Symbol == execSymbol && si.Level == 0); + var parameterSI = symbolInfos.SingleOrDefault(si => si.Symbol == parametersSymbol && si.Level == 0); + var pDefs = parameterSI?.Names.ToDictionary(n => n, n => symbolInfos.SingleOrDefault(si => si.Symbol == n && si.Level > 0)); + var outputSI = symbolInfos.SingleOrDefault(si => si.Symbol == outputSymbol && si.Level == 0); + var oDefs = outputSI?.Names.ToDictionary(n => n, n => symbolInfos.SingleOrDefault(si => si.Symbol == n && si.Level > 0)); + + // assert + Assert.IsNotNull(execSI); + Assert.AreEqual("1st param", pDefs?[p1Symbol].Comment); + Assert.AreEqual("u", pDefs?[p1Symbol].Unit); + Assert.AreEqual("u.v", oDefs?[o2Symbol].Unit); + Assert.AreEqual("2nd output", oDefs?[o2Symbol].Comment); + Assert.IsFalse(oDefs?.ContainsKey(o3LocalSymbol) == true); + Assert.IsNull(oDefs?[o3Symbol]); + } +#endif + +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestInspectTmplSymbols() + { + // arrange + var memoryStream = new MemoryStream(); + var pathToSimLibrary = TestData.SimLibraryDirectory.FullName; + var pathToCode = Path.Combine(pathToSimLibrary, "InspectTmpl", "inspect.R"); + + const string p1Symbol = "p1"; + const string p2Symbol = "p2"; + const string outputSymbol = "o"; + //const string o1Symbol = "o1"; + const string o2Symbol = "o2"; + const string o3Symbol = "o3"; + const string o3LocalSymbol = "Ro3"; + const string o4Symbol = "o4"; + + // act + var symbolInfos = ROpsApi.InspectSymbols(pathToCode); + + var p1SI = symbolInfos.SingleOrDefault(si => si.Symbol == p1Symbol && si.Level == 0); + var p2SI = symbolInfos.SingleOrDefault(si => si.Symbol == p2Symbol && si.Level == 0); + + var outputSI = symbolInfos.SingleOrDefault(si => si.Symbol == outputSymbol && si.Level == 0); + var oDefs = outputSI?.Names.ToDictionary(n => n, n => symbolInfos.SingleOrDefault(si => si.Symbol == n && si.Level > 0)); + var o4SI = symbolInfos.SingleOrDefault(si => si.Symbol == o4Symbol && si.Level == 0); + var o4LocalSI = symbolInfos.SingleOrDefault(si => si.Symbol == o4Symbol && si.Level > 0); + + // assert + Assert.IsNotNull(p1SI); + Assert.AreEqual("1st param", p1SI.Comment); + Assert.IsNotNull(p2SI); + Assert.AreEqual("u", p1SI.Unit); + + Assert.AreEqual("u.v", oDefs?[o2Symbol].Unit); + Assert.AreEqual("2nd output", oDefs?[o2Symbol].Comment); + Assert.IsFalse(oDefs?.ContainsKey(o3LocalSymbol) == true); + Assert.IsNull(oDefs?[o3Symbol]); + Assert.IsNotNull(o4SI); + Assert.AreEqual("u/v", o4LocalSI?.Unit); + } +#endif + } +} diff --git a/Test/RVis.ROps.Test/UnitTestPersistence.cs b/Test/RVis.ROps.Test/UnitTestPersistence.cs new file mode 100644 index 0000000..c1eff74 --- /dev/null +++ b/Test/RVis.ROps.Test/UnitTestPersistence.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RVis.Base.Extensions; +using System.Linq; + +namespace RVis.ROps.Test +{ + [TestClass] + public class UnitTestPersistence + { +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestSerializeRoundTrip() + { + // arrange + var lines = new[] + { + $"list_{nameof(TestSerializeRoundTrip)} <- list(ints = c(1,2,3), nums = c(1.0, 2.0, 3.), alpha = c(\"a\",\"b\",\"c\"))", + $"copy_of_list_{nameof(TestSerializeRoundTrip)} <- list_{nameof(TestSerializeRoundTrip)}" + }; + + // act + ROpsApi.SourceLines(lines); + var bytes = ROpsApi.Serialize($"list_{nameof(TestSerializeRoundTrip)}"); + ROpsApi.EvaluateNonQuery($"rm(list_{nameof(TestSerializeRoundTrip)})"); + ROpsApi.Unserialize(bytes, $"list_{nameof(TestSerializeRoundTrip)}"); + var evaluated = ROpsApi.Evaluate($"all.equal(copy_of_list_{nameof(TestSerializeRoundTrip)}, list_{nameof(TestSerializeRoundTrip)})"); + + // assert + Assert.IsTrue(evaluated.First().Value.First().Resolve(out bool allEqual) && allEqual); + } +#endif + +#if !IS_PIPELINES_BUILD + [TestMethod] + public void TestBinaryRoundTrip() + { + // arrange + var lines = new[] + { + $"list_{nameof(TestBinaryRoundTrip)} <- list(ints = c(1,2,3), nums = c(1.0, 2.0, 3.), alpha = c(\"a\",\"b\",\"c\"))", + $"copy_of_list_{nameof(TestBinaryRoundTrip)} <- list_{nameof(TestBinaryRoundTrip)}" + }; + + // act + ROpsApi.SourceLines(lines); + var bytes = ROpsApi.SaveObjectToBinary($"list_{nameof(TestBinaryRoundTrip)}"); + ROpsApi.EvaluateNonQuery($"rm(list_{nameof(TestBinaryRoundTrip)})"); + ROpsApi.LoadFromBinary(bytes); + var evaluated = ROpsApi.Evaluate($"all.equal(copy_of_list_{nameof(TestBinaryRoundTrip)}, list_{nameof(TestBinaryRoundTrip)})"); + + // assert + Assert.IsTrue(evaluated.First().Value.First().Resolve(out bool allEqual) && allEqual); + } +#endif + } +} diff --git a/Test/RVisUI.Model.Test/Module/UnitTestModuleInfo.cs b/Test/RVisUI.Model.Test/Module/UnitTestModuleInfo.cs new file mode 100644 index 0000000..432bf7e --- /dev/null +++ b/Test/RVisUI.Model.Test/Module/UnitTestModuleInfo.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static LanguageExt.Prelude; + +namespace RVisUI.Model.Test +{ + [TestClass()] + public class UnitTestModuleInfo + { + [TestMethod()] + public void TestConfigurationRoundTrip() + { + // arrange + var moduleInfos = Range(1, 5, 2).Map(id => new ModuleInfo( + $"{id:0000}", + $"name{id:0000}", + $"icon{id:0000}", + $"desc{id:0000}", + Array("pkgA", "pkgB"), + default, + default, + $"{id:0000}.{id:0000}.{id:0000}.{id:0000}", + $"key{id:0000}" + )).ToArr(); + + // act + var configuration = ModuleInfo.GetModuleConfiguration(moduleInfos); + var sortedAndEnabled = ModuleInfo.SortAndEnable(moduleInfos, configuration); + + var moduleInfosLessOne = moduleInfos.RemoveAt(1); + var configurationLessOne = ModuleInfo.GetModuleConfiguration(moduleInfosLessOne); + var sortedAndEnabledLessOne = ModuleInfo.SortAndEnable(moduleInfos, configurationLessOne); + + var moduleInfosReversed = moduleInfos.Reverse(); + var configurationReversed = ModuleInfo.GetModuleConfiguration(moduleInfosReversed); + var sortedAndEnabledReversed = ModuleInfo.SortAndEnable(moduleInfos, configurationReversed); + + // assert + Assert.AreEqual(moduleInfos, sortedAndEnabled); + Assert.IsFalse(sortedAndEnabledLessOne[1].IsEnabled); + Assert.AreEqual(moduleInfosReversed, sortedAndEnabledReversed); + } + } +} \ No newline at end of file diff --git a/Test/RVisUI.Model.Test/Properties/AssemblyInfo.cs b/Test/RVisUI.Model.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0d21801 --- /dev/null +++ b/Test/RVisUI.Model.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("RVisUI.Model.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RVisUI.Model.Test")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("1e9e18d7-ff2b-4bf1-a0cf-45e6a655054e")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Test/RVisUI.Model.Test/RVisUI.Model.Test.csproj b/Test/RVisUI.Model.Test/RVisUI.Model.Test.csproj new file mode 100644 index 0000000..c518a59 --- /dev/null +++ b/Test/RVisUI.Model.Test/RVisUI.Model.Test.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {1E9E18D7-FF2B-4BF1-A0CF-45E6A655054E} + Library + Properties + RVisUI.Model.Test + RVisUI.Model.Test + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + + + + + + + + + {3B88F57E-4C6D-49E4-8D8E-71BC81EA95DA} + RVisUI.Model + + + + + 1.4.0 + + + 1.4.0 + + + + + \ No newline at end of file diff --git a/Test/SimLibrary/CubicExec/.rvis/config.toml b/Test/SimLibrary/CubicExec/.rvis/config.toml new file mode 100644 index 0000000..2219d54 --- /dev/null +++ b/Test/SimLibrary/CubicExec/.rvis/config.toml @@ -0,0 +1,41 @@ +Title = "Cubic Exec" +Description = "Cubic via exec fn" + +[Code] +File = "cubic.R" +Exec = "do_cubic" +Formal = "params" + +[Input] + +[[Input.Parameters]] +Name = "a" +Value = "3" +[[Input.Parameters]] +Name = "b" +Value = "-12" +[[Input.Parameters]] +Name = "c" +Value = "0" +[[Input.Parameters]] +Name = "d" +Value = "50" +[[Input.Parameters]] +Name = "x" +Value = "seq(0, 4, by = 0.4)" + +[Output] + +[[Output.Values]] +Name = "x" + +[[Output.Values.Elements]] +Name = "x" +IsIndependentVariable = true + +[[Output.Values]] +Name = "y" + +[[Output.Values.Elements]] +Name = "y" +IsIndependentVariable = false diff --git a/Test/SimLibrary/CubicExec/cubic.R b/Test/SimLibrary/CubicExec/cubic.R new file mode 100644 index 0000000..6f63038 --- /dev/null +++ b/Test/SimLibrary/CubicExec/cubic.R @@ -0,0 +1,24 @@ +do_cubic <- function(params) { + a <- params[["a"]] + b <- params[["b"]] + c <- params[["c"]] + d <- params[["d"]] + + x <- params[["x"]] + + y <- a * x ^ 3 + b * x ^ 2 + c * x + d + + res <- list(x = x, y = y) + + return(res) +} + +params <- list( + a = 3, + b = -12, + c = 0, + d = 50, + x = seq(0, 4, by = 0.4) + ) + +res <- do_cubic(params) diff --git a/Test/SimLibrary/InspectExec/Inspect.R b/Test/SimLibrary/InspectExec/Inspect.R new file mode 100644 index 0000000..c8fa0b5 --- /dev/null +++ b/Test/SimLibrary/InspectExec/Inspect.R @@ -0,0 +1,41 @@ +get_parameters <- function() { + + # noise + + # 1st param + p1 <- 1 # [u] + + # noise + + # 2nd param + p2 <- 2 # v + + # noise + + return(list(p1 = p1, p2 = p2)) +} + +parameters <- get_parameters() + +run <- function(parameters) { + + i1 <- rep_len(parameters$p1, 10) + i2 <- rep_len(parameters$p2, 10) + + ## noise + # 1st output + o1 <- i1 + i2 # u + + ## noise + # 2nd output + o2 <- i1 * i2 # [u.v] + + ## noise + + # fake diff + Ro3 <- i1 ** i2 # local uses some prefix [u^v] + + return(list(o1 = o1, o2 = o2, o3 = Ro3)) +} + +o <- run(parameters) diff --git a/Test/SimLibrary/InspectTmpl/Inspect.R b/Test/SimLibrary/InspectTmpl/Inspect.R new file mode 100644 index 0000000..a6fa6a0 --- /dev/null +++ b/Test/SimLibrary/InspectTmpl/Inspect.R @@ -0,0 +1,44 @@ +# noise + +# 1st param +p1 <- 1 # [u] + +# noise + +# 2nd param +p2 <- 2 # v + +# noise + +# fake independent var +t <- seq_len(10) + +run <- function() { + + # fake sim seqs + i1 <- rep_len(p1, length(t)) + i2 <- rep_len(p2, length(t)) + + ## noise + # 1st output + o1 <- i1 + i2 # u + + ## noise + # 2nd output + o2 <- i1 * i2 # [u.v] + + ## noise + + # fake diff + Ro3 <- i1 ** i2 # local uses some prefix [u^v] + + # non-state computation + # comp + o4 <- i2 %% i1 # scalar [u/v] + + return(list(o1 = o1, o2 = o2, o3 = Ro3)) +} + +o <- run() + +o4 <- o$o2 %% o$o1 diff --git a/Test/Test.Shared/Test.Shared.projitems b/Test/Test.Shared/Test.Shared.projitems new file mode 100644 index 0000000..b11f666 --- /dev/null +++ b/Test/Test.Shared/Test.Shared.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + c1042840-8833-473b-a4fd-162a68b04d74 + + + Test.Shared + + + + + \ No newline at end of file diff --git a/Test/Test.Shared/Test.Shared.shproj b/Test/Test.Shared/Test.Shared.shproj new file mode 100644 index 0000000..d2b246b --- /dev/null +++ b/Test/Test.Shared/Test.Shared.shproj @@ -0,0 +1,13 @@ + + + + c1042840-8833-473b-a4fd-162a68b04d74 + 14.0 + + + + + + + + diff --git a/Test/Test.Shared/TestData.cs b/Test/Test.Shared/TestData.cs new file mode 100644 index 0000000..200b816 --- /dev/null +++ b/Test/Test.Shared/TestData.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace RVis.Test +{ + internal static class TestData + { + internal static DirectoryInfo _simLibraryDirectory; + internal static DirectoryInfo SimLibraryDirectory + { + get + { + if (null == _simLibraryDirectory) + { + var diTestBin = new DirectoryInfo(Directory.GetCurrentDirectory()); + var diTest = diTestBin.Parent; + while (diTest.Name != nameof(Test)) diTest = diTest.Parent; + var pathToSimLibrary = Path.Combine(diTest.FullName, "SimLibrary"); + _simLibraryDirectory = new DirectoryInfo(pathToSimLibrary); + } + return _simLibraryDirectory; + } + } + } +} diff --git a/UI/Module/Estimation/AssemblyInfo.cs b/UI/Module/Estimation/AssemblyInfo.cs new file mode 100644 index 0000000..8617e7f --- /dev/null +++ b/UI/Module/Estimation/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Estimation.Test")] \ No newline at end of file diff --git a/UI/Module/Estimation/Controls/Dialogs/IterationOptionsDialog.xaml b/UI/Module/Estimation/Controls/Dialogs/IterationOptionsDialog.xaml new file mode 100644 index 0000000..fbc2523 --- /dev/null +++ b/UI/Module/Estimation/Controls/Dialogs/IterationOptionsDialog.xaml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UI/Module/Estimation/Controls/Views/DesignDigestsView.xaml.cs b/UI/Module/Estimation/Controls/Views/DesignDigestsView.xaml.cs new file mode 100644 index 0000000..9e73ee3 --- /dev/null +++ b/UI/Module/Estimation/Controls/Views/DesignDigestsView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Estimation +{ + /// + /// Interaction logic for DesignDigestsView.xaml + /// + public partial class DesignDigestsView : UserControl + { + public DesignDigestsView() + { + InitializeComponent(); + } + } +} diff --git a/UI/Module/Estimation/Controls/Views/DesignView.xaml b/UI/Module/Estimation/Controls/Views/DesignView.xaml new file mode 100644 index 0000000..1da0b78 --- /dev/null +++ b/UI/Module/Estimation/Controls/Views/DesignView.xaml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UI/Module/Estimation/Controls/Views/LikelihoodView.xaml.cs b/UI/Module/Estimation/Controls/Views/LikelihoodView.xaml.cs new file mode 100644 index 0000000..d31dce6 --- /dev/null +++ b/UI/Module/Estimation/Controls/Views/LikelihoodView.xaml.cs @@ -0,0 +1,15 @@ +using System.Windows.Controls; + +namespace Estimation +{ + /// + /// Interaction logic for PriorsView.xaml + /// + public partial class LikelihoodView : UserControl + { + public LikelihoodView() + { + InitializeComponent(); + } + } +} diff --git a/UI/Module/Estimation/Controls/Views/PosteriorView.xaml b/UI/Module/Estimation/Controls/Views/PosteriorView.xaml new file mode 100644 index 0000000..f0e375e --- /dev/null +++ b/UI/Module/Estimation/Controls/Views/PosteriorView.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UI/Module/Estimation/Controls/Views/PosteriorView.xaml.cs b/UI/Module/Estimation/Controls/Views/PosteriorView.xaml.cs new file mode 100644 index 0000000..6241fef --- /dev/null +++ b/UI/Module/Estimation/Controls/Views/PosteriorView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Estimation +{ + /// + /// Interaction logic for FitView.xaml + /// + public partial class PosteriorView : UserControl + { + public PosteriorView() + { + InitializeComponent(); + } + } +} diff --git a/UI/Module/Estimation/Controls/Views/PriorsView.xaml b/UI/Module/Estimation/Controls/Views/PriorsView.xaml new file mode 100644 index 0000000..e21f145 --- /dev/null +++ b/UI/Module/Estimation/Controls/Views/PriorsView.xaml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +