Skip to content

Commit 1dbf724

Browse files
committed
MCSim; zoom; line/scatter
1 parent 3f6cbf1 commit 1dbf724

File tree

59 files changed

+1840
-140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1840
-140
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Company>HSE</Company>
44
<Product>RVis</Product>
55
<Copyright>Copyright © HSE 2020</Copyright>
6-
<AssemblyVersion>0.9.11068.2</AssemblyVersion>
7-
<FileVersion>0.9.11068.2</FileVersion>
6+
<AssemblyVersion>0.10.11068.0</AssemblyVersion>
7+
<FileVersion>0.10.11068.0</FileVersion>
88
</PropertyGroup>
99
</Project>

RVis.Model/Code/ManagedImport.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public async Task<string> ImportExecToLibraryAsync(IRVisClient rVisClient)
200200

201201
CheckTrace(trace);
202202

203-
return _simLibrary.Import(_pathToContainingDirectory);
203+
return _simLibrary.ImportRSimulation(_pathToContainingDirectory);
204204
}
205205

206206
public async Task<string> ImportTmplToLibraryAsync(IRVisClient rVisClient)
@@ -266,7 +266,7 @@ public async Task<string> ImportTmplToLibraryAsync(IRVisClient rVisClient)
266266

267267
File.WriteAllLines(pathToRFile, codeLines);
268268

269-
return _simLibrary.Import(_pathToContainingDirectory);
269+
return _simLibrary.ImportRSimulation(_pathToContainingDirectory);
270270
}
271271

272272
protected virtual void Dispose(bool disposing)
@@ -289,9 +289,14 @@ protected virtual void Dispose(bool disposing)
289289

290290
private void SetUpStaging()
291291
{
292-
var containingDirectoryName = DateTime.UtcNow.ToString("o", InvariantCulture).ToValidFileName();
293-
var pathToContainingDirectory = Path.Combine(Path.GetTempPath(), containingDirectoryName);
294-
Directory.CreateDirectory(pathToContainingDirectory);
292+
string pathToContainingDirectory;
293+
do
294+
{
295+
pathToContainingDirectory = Combine(GetTempPath(), GetRandomFileName());
296+
}
297+
while (Directory.Exists(pathToContainingDirectory));
298+
299+
RequireNotNull(Directory.CreateDirectory(pathToContainingDirectory));
295300

296301
var pathToRFile = Path.Combine(pathToContainingDirectory, _codeFileName);
297302

RVis.Model/Extensions/SimExt.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using LanguageExt;
22
using Nett;
33
using RVis.Base.Extensions;
4+
using System;
45
using System.Diagnostics;
56
using System.IO;
67
using static LanguageExt.Prelude;
@@ -11,6 +12,12 @@ namespace RVis.Model.Extensions
1112
{
1213
public static partial class SimExt
1314
{
15+
internal static bool IsRSimulation(this Simulation simulation) =>
16+
simulation.SimConfig.SimCode.File.EndsWith(".R", StringComparison.InvariantCultureIgnoreCase);
17+
18+
internal static bool IsMCSimSimulation(this Simulation simulation) =>
19+
simulation.SimConfig.SimCode.File.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase);
20+
1421
internal static Option<SimConfig> LoadConfig(this Simulation simulation, string inputHash)
1522
{
1623
if (simulation.SimConfig.SimInput.Hash == inputHash) return Some(simulation.SimConfig);
@@ -37,7 +44,7 @@ public static bool IsExecType(this Simulation simulation) =>
3744
public static bool IsTmplType(this Simulation simulation) =>
3845
!simulation.IsExecType();
3946

40-
internal static void WriteToFile(this SimConfig config, string pathToSimulation)
47+
public static void WriteToFile(this SimConfig config, string pathToSimulation)
4148
{
4249
var pathToPrivate = Path.Combine(pathToSimulation, PRIVATE_SUBDIRECTORY);
4350
if (!Directory.Exists(pathToPrivate)) Directory.CreateDirectory(pathToPrivate);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using LanguageExt;
2+
using System;
3+
using static System.Environment;
4+
using static System.String;
5+
6+
namespace RVis.Model
7+
{
8+
public sealed class MCSimExecutionException : Exception
9+
{
10+
public MCSimExecutionException(string message, int exitCode, Arr<string> diagnostics) : base(message)
11+
{
12+
ExitCode = exitCode;
13+
Diagnostics = diagnostics;
14+
}
15+
16+
public int ExitCode { get; }
17+
18+
public Arr<string> Diagnostics { get; }
19+
20+
public override string ToString() =>
21+
$"{Message}{NewLine}{NewLine}" +
22+
$"Diagnostics:{NewLine}{NewLine}" +
23+
$"{Join(NewLine, Diagnostics)}{NewLine}{NewLine}" +
24+
$"{StackTrace.ToString()}";
25+
}
26+
}

RVis.Model/MCSim/MCSimExecutor.cs

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
using LanguageExt;
2+
using RVis.Data;
3+
using Scriban;
4+
using Scriban.Runtime;
5+
using Scriban.Syntax;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Threading;
11+
using static RVis.Base.Check;
12+
using static System.Double;
13+
using static System.Environment;
14+
using static System.IO.Directory;
15+
using static System.IO.File;
16+
using static System.IO.Path;
17+
using static System.String;
18+
using Directory = System.IO.Directory;
19+
using File = System.IO.File;
20+
21+
namespace RVis.Model
22+
{
23+
public sealed class MCSimExecutor : IDisposable
24+
{
25+
public MCSimExecutor(string pathToSimulation, SimConfig config)
26+
{
27+
RequireDirectory(pathToSimulation);
28+
29+
var pathToExecutable = Combine(pathToSimulation, config.SimCode.File);
30+
RequireFile(pathToExecutable);
31+
32+
var templateInFileName = GetFileNameWithoutExtension(config.SimCode.File) + ".in";
33+
var pathToTemplateIn = Combine(pathToSimulation, templateInFileName);
34+
RequireFile(pathToTemplateIn);
35+
36+
var templateIn = ReadAllText(pathToTemplateIn);
37+
_templateIn = Template.ParseLiquid(templateIn);
38+
39+
var scriptVariables = _templateIn.Page.Body.Statements
40+
.OfType<ScriptExpressionStatement>()
41+
.Select(ses => ses.Expression)
42+
.OfType<ScriptVariable>()
43+
.Select(e => e.Name)
44+
.OrderBy(s => s)
45+
.ToArr();
46+
var configParameters = config.SimInput.SimParameters.Map(p => p.Name);
47+
48+
if (scriptVariables != configParameters)
49+
{
50+
var missingScriptVariables = configParameters.Filter(p => !scriptVariables.Contains(p));
51+
RequireTrue(
52+
missingScriptVariables.IsEmpty,
53+
"Missing parameters in .in file: " + Join(", ", missingScriptVariables)
54+
);
55+
56+
var missingConfigParameters = scriptVariables.Filter(p => !configParameters.Contains(p));
57+
RequireTrue(
58+
missingConfigParameters.IsEmpty,
59+
"Unknown parameters in .in file: " + Join(", ", missingConfigParameters)
60+
);
61+
}
62+
63+
do
64+
{
65+
_pathToInOutDirectory = Combine(GetTempPath(), GetRandomFileName());
66+
}
67+
while (Directory.Exists(_pathToInOutDirectory));
68+
69+
RequireNotNull(CreateDirectory(_pathToInOutDirectory));
70+
71+
_pathToInFile = Combine(
72+
_pathToInOutDirectory,
73+
GetFileNameWithoutExtension(pathToExecutable) + ".in");
74+
75+
_pathToOutFile = Combine(
76+
_pathToInOutDirectory,
77+
GetFileNameWithoutExtension(pathToExecutable) + ".out");
78+
79+
_processStartInfo = new ProcessStartInfo
80+
{
81+
UseShellExecute = false,
82+
CreateNoWindow = true,
83+
RedirectStandardOutput = true,
84+
RedirectStandardError = true,
85+
FileName = pathToExecutable,
86+
Arguments = _pathToInFile + " " + _pathToOutFile
87+
};
88+
89+
_independentVariableName = config.SimOutput.IndependentVariable.Name;
90+
_dependentVariableNames = config.SimOutput.DependentVariables.Map(v => v.Name);
91+
_tableName = config.Title;
92+
_defaultSource = config.SimInput.SimParameters;
93+
}
94+
95+
public void Dispose() => Dispose(true);
96+
97+
public NumDataTable Execute(Arr<SimParameter> parameters)
98+
{
99+
if (!Monitor.TryEnter(_syncLock))
100+
{
101+
throw new InvalidOperationException("Executor is busy");
102+
}
103+
104+
try
105+
{
106+
return _Execute(parameters);
107+
}
108+
finally
109+
{
110+
Monitor.Exit(_syncLock);
111+
}
112+
}
113+
114+
private NumDataTable _Execute(Arr<SimParameter> parameters)
115+
{
116+
File.Delete(_pathToOutFile);
117+
118+
var source = _defaultSource.Map(
119+
p => parameters.Find(pp => pp.Name == p.Name).IfNone(p)
120+
);
121+
122+
var scriptObject = new ScriptObject();
123+
source.Iter(p => scriptObject.Add(p.Name, p.Value));
124+
125+
var context = new TemplateContext();
126+
context.PushGlobal(scriptObject);
127+
128+
var @in = _templateIn.Render(context);
129+
130+
WriteAllText(_pathToInFile, @in);
131+
132+
using var run = Process.Start(_processStartInfo);
133+
134+
run.WaitForExit();
135+
136+
if (run.ExitCode != 0)
137+
{
138+
var stdOut = run.StandardOutput.ReadToEnd();
139+
var stdErr = run.StandardError.ReadToEnd();
140+
return HandleFailure(run.ExitCode, stdOut, stdErr);
141+
}
142+
143+
if (!File.Exists(_pathToOutFile))
144+
{
145+
var stdOut = run.StandardOutput.ReadToEnd();
146+
return HandleIntegrationFailure(stdOut);
147+
}
148+
149+
return ProcessOutFile();
150+
}
151+
152+
private NumDataTable ProcessOutFile()
153+
{
154+
var @out = ReadAllLines(_pathToOutFile);
155+
156+
RequireTrue(@out[0].StartsWith("Results"), "Unexpected out file format");
157+
158+
var line = 1;
159+
160+
while (line < @out.Length && !@out[line].StartsWith(_independentVariableName)) ++line;
161+
162+
if (line == @out.Length) return default;
163+
164+
var outputNames = @out[line]
165+
.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries)
166+
.ToArr();
167+
168+
RequireTrue(
169+
outputNames.Skip(1).OrderBy(n => n).SequenceEqual(_dependentVariableNames),
170+
$"Expected output columns: {Join(", ", _dependentVariableNames)}. Received output columns: {Join(", ", outputNames.Skip(1))}"
171+
);
172+
173+
var nRows = @out.Length - line - 1;
174+
175+
var outputColumns = new SortedDictionary<string, List<double>>();
176+
outputNames.Iter(@on => outputColumns.Add(on, new List<double>(nRows)));
177+
178+
++line;
179+
180+
while (line < @out.Length)
181+
{
182+
var row = @out[line].Trim();
183+
184+
if (row.Length == 0) break;
185+
186+
var values = row
187+
.Split(new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries)
188+
.Map(v => TryParse(v, out double d) ? d : NaN)
189+
.ToArr();
190+
191+
outputNames.Iter((i, @on) => outputColumns[@on].Add(values[i]));
192+
193+
++line;
194+
}
195+
196+
RequireTrue(
197+
outputColumns.Values
198+
.Skip(1)
199+
.All(v => v.Count == outputColumns.Values.First().Count)
200+
);
201+
202+
var table = new NumDataTable(
203+
_tableName,
204+
outputNames.Map(@on => new NumDataColumn(on, outputColumns[@on]))
205+
);
206+
207+
return table;
208+
}
209+
210+
private NumDataTable HandleFailure(int exitCode, string stdOut, string stdErr)
211+
{
212+
static Arr<string> ExtractErrors(string s)
213+
{
214+
const string PREFIX = "Error:";
215+
216+
var lines = s.Split(new[] { NewLine }, StringSplitOptions.RemoveEmptyEntries);
217+
218+
return lines
219+
.Where(l => l.StartsWith(PREFIX))
220+
.Select(l => l.Substring(PREFIX.Length).Trim())
221+
.ToArr();
222+
}
223+
224+
var errorsFromOut = ExtractErrors(stdOut);
225+
var errorsFromErr = ExtractErrors(stdErr);
226+
227+
throw new MCSimExecutionException(
228+
$"MCSim Fault ({Convert.ToString(exitCode, 2)})",
229+
exitCode,
230+
errorsFromOut + errorsFromErr
231+
);
232+
}
233+
234+
private NumDataTable HandleIntegrationFailure(string stdOut)
235+
{
236+
const string START_PREFIX = "Doing analysis";
237+
238+
var lines = stdOut.Split(new[] { NewLine }, StringSplitOptions.RemoveEmptyEntries);
239+
var startIndex = 0;
240+
241+
while (!lines[startIndex].StartsWith(START_PREFIX) && ++startIndex < lines.Length) ;
242+
243+
if (++startIndex >= lines.Length)
244+
{
245+
throw new MCSimExecutionException("Unknown integration fault", 0, default);
246+
}
247+
248+
const string END_PREFIX = "Done";
249+
250+
var endIndex = startIndex;
251+
252+
while (!lines[endIndex].StartsWith(END_PREFIX) && ++endIndex < lines.Length) ;
253+
254+
if (endIndex >= lines.Length)
255+
{
256+
throw new MCSimExecutionException("Unknown integration fault", 0, default);
257+
}
258+
259+
var diagnostics = lines.Skip(startIndex).Take(endIndex - startIndex).ToArr();
260+
261+
throw new MCSimExecutionException("MCSim failed to produce data", 0, diagnostics);
262+
}
263+
264+
private void Dispose(bool disposing)
265+
{
266+
if (!_disposed)
267+
{
268+
if (disposing)
269+
{
270+
271+
}
272+
273+
try
274+
{
275+
Delete(_pathToInOutDirectory, recursive: true);
276+
}
277+
catch (Exception) { }
278+
279+
_disposed = true;
280+
}
281+
}
282+
283+
private readonly Template _templateIn;
284+
private readonly string _pathToInOutDirectory;
285+
private readonly string _pathToInFile;
286+
private readonly string _pathToOutFile;
287+
private readonly ProcessStartInfo _processStartInfo;
288+
private readonly string _independentVariableName;
289+
private readonly Arr<string> _dependentVariableNames;
290+
private readonly string _tableName;
291+
private readonly Arr<SimParameter> _defaultSource;
292+
private readonly object _syncLock = new object();
293+
private bool _disposed = false;
294+
}
295+
}

0 commit comments

Comments
 (0)