Example #1
0
 private string PathToModels()
 {
     if (RDocker.UseDocker())
     {
         // This is the expected path to the models executable in the docker
         // image. Nasty stuff.
         return("/opt/apsim/Models");
     }
     return(typeof(IModel).Assembly.Location);
 }
Example #2
0
        /// <summary>
        /// Generates the R script which performs the optimization.
        /// </summary>
        /// <param name="fileName">File path to which the R code will be saved.</param>
        /// <param name="outputPath">Directory/path to which output results will be saved. This is passed as a parameter to croptimizR.</param>
        /// <param name="apsimxFileName">Name of the .apsimx file to be run by the optimisation.</param>
        private void GenerateRScript(string fileName, string outputPath, string apsimxFileName)
        {
            // tbi: package installation. Need to test on a clean VM.
            StringBuilder contents = new StringBuilder();

            contents.AppendLine($"variable_names <- c({string.Join(", ", VariableNames.Select(x => $"'{x.Trim()}'").ToArray())})");

            // In theory, it would be better to always use relative path to
            // the input file, by setting the working directory of the R
            // process appropriately. Unfortunately, this will require some
            // refactoring of the R wrapper which I don't really want to do
            // right now. So for now I'm going to just use relative path if
            // using docker.
            if (RDocker.UseDocker())
            {
                apsimxFileName = Path.GetFileName(apsimxFileName);
            }

            // If we're reading from the PredictedObserved table, need to fix
            // Predicted./Observed. suffix for the observed variables.
            string escapedOutputPath = outputPath.Replace(@"\", "/");

            string[] sanitisedObservedVariables = GetObservedVariableName().Select(x => $"'{x.Trim()}'").ToArray();
            string   dateVariable = VariableNames.Any(v => v.StartsWith("Predicted.")) ? "Predicted.Clock.Today" : "Clock.Today";

            contents.AppendLine($"observed_variable_names <- c({string.Join(", ", sanitisedObservedVariables)}, '{dateVariable}')");
            contents.AppendLine($"apsimx_path <- '{PathToModels().Replace(@"\", "/")}'");
            contents.AppendLine($"apsimx_file <- '{apsimxFileName.Replace(@"\", "/")}'");
            contents.AppendLine($"simulation_names <- {GetSimulationNames()}");
            contents.AppendLine($"predicted_table_name <- '{PredictedTableName}'");
            contents.AppendLine($"observed_table_name <- '{ObservedTableName}'");
            contents.AppendLine($"param_info <- {GetParamInfo()}");
            contents.AppendLine();
            contents.AppendLine(OptimizationMethod.GenerateOptimizationOptions("optim_options"));
            contents.AppendLine($"optim_options$path_results <- '{escapedOutputPath}'");
            if (RandomSeed != null)
            {
                contents.AppendLine($"optim_options$ranseed <- {RandomSeed}");
            }
            contents.AppendLine();
            contents.AppendLine($"crit_function <- {OptimizationMethod.CritFunction}");
            contents.AppendLine($"optim_method <- '{OptimizationMethod.ROptimizerName}'");
            contents.AppendLine();
            contents.AppendLine(ReflectionUtilities.GetResourceAsString("Models.Resources.RScripts.OptimizR.r"));

            // Don't use Path.Combine() - as this may be running in a (linux) docker container.
            // R will work with forward slashes for path separators on win and linux,
            // but backslashes will not work on linux.
            string rDataExpectedPath = $"{escapedOutputPath}/optim_results.Rdata";

            contents.AppendLine(CreateReadRDataScript(rDataExpectedPath));

            File.WriteAllText(fileName, contents.ToString());
        }
Example #3
0
        /// <summary>
        /// Run the optimization (and wait for it to finish).
        /// </summary>
        /// <param name="cancelToken">Cancellation token.</param>
        public void Run(CancellationTokenSource cancelToken)
        {
            Progress = 0;

            Status = "Generating R Script";
            string fileName = GetTempFileName("parameter_estimation.r");

            string outputPath = GetWorkingDirectory();

            if (!Directory.Exists(outputPath))
            {
                Directory.CreateDirectory(outputPath);
            }

            string apsimxFileName = GenerateApsimXFile();

            // If running with docker, all references to the output path must
            // be relative (ie '.'). It would be nice to be able to do this with
            // native R runner as well but that will require some refactoring work.
            string outputReferencePath = RDocker.UseDocker() ? "." : outputPath;

            GenerateRScript(fileName, outputReferencePath, apsimxFileName);

            if (RDocker.UseDocker())
            {
                // todo: we should really be reporting warnings/errors here.
                // However any summary file will not have its links connected,
                // due to the way that CroptimizR is run. This needs further thought.
                IR client = new RDocker(
                    outputHandler: OnOutputReceived
                    // warningHandler: w => FindInScope<ISummary>()?.WriteMessage(this, w, MessageType.Warning),
                    // errorHandler: e => FindInScope<ISummary>()?.WriteMessage(this, e, MessageType.Error)
                    );

                Status = "Running Parameter Optimization";
                try
                {
                    client.RunScriptAsync(fileName, new List <string>(), cancelToken.Token).Wait();
                }
                catch (AggregateException errors)
                {
                    // Don't propagate task canceled exceptions.
                    if (errors.InnerExceptions.Count == 1 && errors.InnerExceptions[0] is TaskCanceledException)
                    {
                        return;
                    }
                    throw;
                }
            }
            else
            {
                Status = "Installing R Packages";
                R r = new R(cancelToken.Token);
                r.InstallPackages("remotes", "dplyr", "nloptr", "DiceDesign", "DBI", "cli");
                r.InstallFromGithub("hol430/ApsimOnR", "SticsRPacks/CroptimizR");


                // todo - capture stderr as well?
                r.OutputReceived += OnOutputReceivedFromR;
                string stdout = r.Run(fileName);
                r.OutputReceived -= OnOutputReceivedFromR;
                WriteMessage(stdout);
            }

            // Copy output files into appropriate output directory, if one is specified. Otherwise, delete them.
            Status = "Reading Output";
            DataTable output        = null;
            string    apsimxFileDir = FindAncestor <Simulations>()?.FileName;

            if (string.IsNullOrEmpty(apsimxFileDir))
            {
                apsimxFileDir = FindAncestor <Simulation>()?.FileName;
            }
            if (!string.IsNullOrEmpty(apsimxFileDir))
            {
                apsimxFileDir = Path.GetDirectoryName(apsimxFileDir);
            }

            IDataStore storage   = FindInScope <IDataStore>();
            bool       firstFile = true;

            foreach (string file in Directory.EnumerateFiles(outputPath))
            {
                if (Path.GetFileName(file) == outputCsvFileName)
                {
                    if (storage != null && storage.Writer != null)
                    {
                        output = ReadRData(file);

                        storage.Writer.WriteTable(output, deleteAllData: firstFile);
                        firstFile = false;
                    }
                }
                if (!string.IsNullOrEmpty(apsimxFileDir))
                {
                    File.Copy(file, Path.Combine(apsimxFileDir, Path.Combine($"{Name}-{Path.GetFileName(file)}")), true);
                }
            }

            // Now, we run the simulations with the optimal values, and store
            // the results in a checkpoint called 'After'. Checkpointing has
            // not been implemented on the sockets storage implementation.
            if (output != null && FindInScope <IDataStore>().Writer is DataStoreWriter)
            {
                Status = "Running simulations with optimised parameters";
                IEnumerable <CompositeFactor> optimalValues = GetOptimalValues(output);
                RunSimsWithOptimalValues(apsimxFileName, "Optimal", optimalValues);

                // Now run sims without optimal values, to populate the 'Current' checkpoint.
                Status = "Running simulations";
                Runner           runner = new Runner(Children);
                List <Exception> errors = runner.Run();
                if (errors != null && errors.Count > 0)
                {
                    throw errors[0];
                }
            }

            // Delete temp outputs.
            Directory.Delete(outputPath, true);
        }