private void RunParallel(SpineAdmin spineManager)
        {
            string frac = reportingFraction == 100 ? "%" : "/" + reportingFraction;

            foreach (FunOutOfSpineBase fun in spineManager.runBeforeSpine)
            {
                fun.Run();                                                            // run pre-spine functions (e.g. Keep/DropUnit, etc.)
            }
            int totalHHs = infoStore.hhAdmin.hhs.Count;
            int currentHH = 0; int currentPerc = 0;

            Parallel.ForEach <HH>(infoStore.hhAdmin.hhs, hh => // for each household run spine-functions (BenCalc, Elig, etc.)
            {
                int curFunIndex = 0;
                while (true)
                {
                    FunInSpineBase fun = spineManager.GetNextFun(ref curFunIndex, hh) as FunInSpineBase; if (fun == null)
                    {
                        break;
                    }
                    foreach (List <Person> tu in hh.GetTUs(fun.GetTUName())) // loop over TUs within household
                    {
                        fun.Run(hh, tu);
                    }
                }

                Interlocked.Increment(ref currentHH);
                if (reportingFraction * currentHH / totalHHs > currentPerc)   // use int division here!
                {
                    currentPerc = reportingFraction * currentHH / totalHHs;
                    if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
                    {
                        id = "Running spine",
                        message = $"Running spine.. {currentPerc}{frac}"
                    }))
                    {
                        throw new OperationCanceledException(); // note: one cannot simply break a Parallel.ForEach
                    }
                }
            });

            foreach (FunOutOfSpineBase fun in spineManager.runAfterSpine)
            {
                fun.Run();                                                           // run post-spine functions (DefOutput, etc.)
            }
        }
Beispiel #2
0
        private bool RunSequential(SpineAdmin spineManager)
        {
            int curFunIndex = 0;

            Dictionary <FunBase, List <FunUnitLoop> > unitLoopRanges = spineManager.GetUnitLoopRangesForSequentialRun();

            while (true) // loop over functions
            {
                FunBase fun = spineManager.GetNextFun(ref curFunIndex); if (fun == null)
                {
                    break;
                }

                DefFun.RUN_MODE runMode = // find out if the function expects to be run within HH-loop or not
                                          DefinitionAdmin.GetFunDefinition(fun.description.GetFunName()).runMode;
                if (runMode != DefFun.RUN_MODE.IN_SPINE)
                {
                    (fun as FunOutOfSpineBase).Run(); // concerns functions usually running pre- or post-spine (DefOutput, Totals, etc.)
                }
                else
                {
                    // concerns functions usually running in spine (BenCalc, Elig, etc.)
                    FunInSpineBase spineFun = fun as FunInSpineBase;
                    if (infoStore.runConfig.forceSequentialRun)
                    {
                        // run in a single thread
                        foreach (HH hh in infoStore.hhAdmin.hhs)
                        {
                            foreach (List <Person> tu in hh.GetTUs(spineFun.GetTUName()))
                            {
                                SpineFunRun(spineFun, hh, tu);
                            }
                        }
                    }
                    else
                    {
                        // run in multiple threads
                        Parallel.ForEach <HH>(infoStore.hhAdmin.hhs, hh => {
                            foreach (List <Person> tu in hh.GetTUs(spineFun.GetTUName()))
                            {
                                SpineFunRun(spineFun, hh, tu);
                            }
                        });
                    }
                }
                if (!infoStore.communicator.ReportProgress(
                        new Communicator.ProgressInfo()
                {
                    message = $"Done with function {fun.description.Get(true)}"
                }))
                {
                    return(false);
                }
            }
            return(true);

            void SpineFunRun(FunInSpineBase spineFun, HH hh, List <Person> tu)
            {
                bool run = false;

                if (unitLoopRanges.ContainsKey(spineFun))                // there are UnitLoops in the spine
                {                                                        // note that a function may be enclosed by more than one UnitLoop (see NRR add-on)
                    foreach (FunUnitLoop ul in unitLoopRanges[spineFun]) // if any of the UnitLoops allows running - run
                    {
                        if (!ul.IsFinished(hh))
                        {
                            run = true;
                        }
                    }
                }
                else
                {
                    run = true;  // the usual case - no UnitLoops
                }
                if (run)
                {
                    spineFun.Run(hh, tu);
                }
            }
        }
        /// <summary>
        /// runs the executable (after configuration is set by either "exe"- or "lib"-call)
        /// *** NOTE ON ERROR HANDLING ***
        /// each of the called functions is expected to handle errors as follows:
        /// - critical error (further programme execution is not possible or does not make sense): throw informative(!) exception
        /// - errors and warnings: use Communicator.ReportError (amongst others indicating whether it is an error or a warning)
        /// thus the caller gets the errors and warnings and can handle them as it wants (issue in a box, put to an error-log-file, ...)
        /// in addition the errors are counted by the Communicator, allowing the Run-function to stop, e.g. after reading, if there are errors
        /// </summary>
        internal bool Run()
        {
            EM_Executable_Test.infoStore            = infoStore; // initialise the testing-class
            infoStore.communicator.maxRunTimeErrors = infoStore.runConfig.maxRunTimeErrors;
            // if output should be in memory, initiate the output dictionary
            if (infoStore.runConfig.returnOutputInMemory)
            {
                infoStore.output = new Dictionary <string, System.Text.StringBuilder>();
            }

            // READ COUNTRY FILE
            // note on case-sensitivity: parameter values are converted to lower-case, everything else is original-case (to keep open for more case-sensitivity in future)
            infoStore.country = ExeXmlReader.ReadCountry(
                infoStore.runConfig.pathHandler.GetCountryFilePath(infoStore.runConfig.countryShortName),
                infoStore.runConfig.sysID, infoStore.runConfig.dataID, // ids (or names) of system and data
                infoStore.runConfig.ignorePrivate,                     // ignore (do not read) private policies/functions/parameters
                infoStore.communicator);                               // see note on error-handling above
            if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id = Communicator.EXEPROG_PAR_READ,
                message = $"{infoStore.country.sys.name.ToUpper()} parameters read", detailedInfo = ComposeRunInfo()
            }))
            {
                return(false);
            }

            // READ VARIABLES FILE (this is a prerequisite for formula interpretation)
            // note on case-sensitivity: the indexVarConfig-dictionary has a case-insensitive key-comparer
            infoStore.operandAdmin.indexVarConfig = ExeXmlReader.ReadVars(
                infoStore.runConfig.pathHandler.GetVarFilePath(),
                infoStore.communicator);
            if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id = Communicator.EXEPROG_VAR_READ,
                message = $"{infoStore.operandAdmin.indexVarConfig.Count} definitions read from the Variables file"
            }))
            {
                return(false);
            }

            // READ OTHER GLOBAL FILES
            infoStore.exRate = ExeXmlReader.ReadExRate( // note: returns -1 if not available (which may be ok if not used)
                infoStore.runConfig.pathHandler.GetExRatesFilePath(),
                infoStore.country.cao.shortName,
                infoStore.country.sys.name,           // name of system allows selecting the suitable exchange rate
                infoStore.runConfig.dateExchangeRate, // e.g. TAGS.EXRATE_JUNE30 or TAGS.DEFAULT ...
                infoStore.communicator);

            // note on case-sensitivity: the upFacs-dictionary has a case-insensitive key-comparer
            ExeXml.UpIndDict hicp = ExeXmlReader.ReadHICP( // note: returns hicp.data/hicp.sys=null if not available (which may be ok if not used)
                infoStore.runConfig.pathHandler.GetHICPFilePath(),
                infoStore.country.cao.shortName,
                infoStore.country.sys.year,   // ids of system and data allow returning
                infoStore.country.data.year,  // the relevant values
                infoStore.communicator);
            if (infoStore.country.upFacs.ContainsKey(DefPar.Value.HICP))
            {
                infoStore.country.upFacs[DefPar.Value.HICP] = hicp;
            }
            else
            {
                infoStore.country.upFacs.Add(DefPar.Value.HICP, hicp);
            }
            if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id = Communicator.EXEPROG_GLOB_PAR_READ,
                message = $"Global parameters read"
            }))
            {
                return(false);
            }

            // MAKE UPRATING FACTORS AVAILABLE AS GLOBAL VARIABLES, 1st step: generation (to be filled with values once data is read)
            foreach (var upFac in infoStore.country.upFacs)
            {
                infoStore.operandAdmin.RegisterVar(upFac.Key.ToLower(),
                                                   DefFun.DefConst, // put DefConst as "creator" mainly to allow overwritting by DefConst (done in CZ), but can be justified by it was like this in the old exe
                                                   null, false, true, false, true);
            }

            // MAKE INDIRECT TAXES AVAILABLE AS GLOBAL VARIABLES, 1st step (same procedure as for uprating factors)
            foreach (var indTax in infoStore.country.indTaxes)
            {
                infoStore.operandAdmin.RegisterVar(indTax.Key.ToLower(), "IndirectTaxTable", null, false, true, false, true);
            }

            // IF APPROPRIATE, READ ADD-ONS
            // note: read here to have extension switches (AddOn_ExtensionSwitch) at disposal, as they overwrite any other switches
            // even those set via run-tool, justification: the add-on creator probably knows why she wants sth on or off
            // add-ons are still integrated after "switching" (i.e. split from reading) as there may be still (old) "manual" switching (via ChangeParam)
            List <ExeXml.AddOn> xmlAddOns = new List <ExeXml.AddOn>();

            if (infoStore.runConfig.addOns.Count > 0)
            {
                foreach (var addOn in infoStore.runConfig.addOns)
                {
                    xmlAddOns.Add(ExeXmlReader.ReadAddOn(
                                      path: infoStore.runConfig.pathHandler.GetAddOnFilePath(addOn.Key),
                                      addOnSysIdentifier: addOn.Value,
                                      communicator: infoStore.communicator));
                }
                if (infoStore.communicator.errorCount > 0)
                {
                    return(false);
                }
                if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
                {
                    id = Communicator.EXEPROG_ADDON_READ,
                    message = $"Add-on info read"
                }))
                {
                    return(false);
                }
            }

            // if the "All Output in €" box is checked in the run-tool
            if (infoStore.runConfig.forceOutputEuro)
            {
                infoStore.country.sys.isOutputEuro = true;
            }

            // APPLY GLOBAL SWITCHES IN COMBINATION WITH ADD-ON- AND CONFIG-SETTINGS
            HandleExtensionSwitches(xmlAddOns);

            // IF APPROPRIATE, INTEGRATE ADD-ON
            if (xmlAddOns.Count > 0)
            {
                IntegrateAddOns(xmlAddOns);
                if (infoStore.communicator.errorCount > 0)
                {
                    return(false);
                }
                if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
                {
                    id = Communicator.EXEPROG_ADDON_INTEG,
                    message = $"Add-on info integrated"
                }))
                {
                    return(false);
                }
            }

            // IF APPROPRIATE, HANDLE PARAMETER MODIFICATIONS VIA JSON-FILE
            if (!string.IsNullOrEmpty(infoStore.runConfig.pathParModifications))
            {
                HandleParModificationsByFile();
            }

            // IF APPROPRIATE, HANDLE PARAMETER MODIFICATIONS VIA JSON-STRING
            if (!string.IsNullOrEmpty(infoStore.runConfig.stringParModifications))
            {
                HandleParModificationsByString();
            }

            // IF APPROPRIATE, REPLACE =cc= AND =sys= BY COUNTRY-SHORTNAME RESPECTIVELY SYSTEM-NAME
            ReplaceCCSys(infoStore.country.cao.pols);

            // IF APPROPRIATE, APPLY ChangeParam AND ChangeSwitch
            ApplyChangeParam(); ApplyChangeSwitch();

            // SWITCH OFF SetDefaults AND Uprates IF THEY DO NOT APPLY TO THE CURRENT DATASET
            SwitchOffUprateSetDefault();

            // IF APPROPRIATE, handle break function
            if (!HandleBreak())
            {
                return(false);                // stop if the break is misdefined
            }
            // DROP EVERYTHING THAT IS OFF OR N/A (with the intention to enhance run-time-speed by not having to ask for it)
            DropOff();

            // GET VARIABLES CONTAINED IN INPUT DATA (we need to do this upfront as the info is required for addressing variables with wildcards)
            HHAdmin.GetAllDataVariables(infoStore);

            // IF APPROPRIATE, REGISTER EXPENDITURE VARIABLES
            if (infoStore.country.data.readXVar)
            {
                RegisterXVar(infoStore.allDataVariables);
            }

            // IF APPROPRIATE, REGISTER STRING VARIABLES IN INPUT DATA, WHICH ARE TO BE TRANSFERRED TO OUTPUT
            RegisterOutputStringVar();

            // CHECK AND ANALYSE THE OBTAINED INFO AND PREPARE FOR RUN
            CheckAndPrepare();

            // check for availability of applicable Uprate functions
            if (infoStore.applicableUprateFunctions.Count != 1)
            {
                string m = string.Empty;
                foreach (Description d in infoStore.applicableUprateFunctions)
                {
                    m += d.Get() + " & ";
                }
                if (m == string.Empty)
                {
                    m = $"No applicable {DefFun.Uprate} function found";
                }
                else
                {
                    m = $"More than one applicable {DefFun.Uprate} function found: " + m.TrimEnd(new char[] { '&', ' ' });
                }
                infoStore.communicator.ReportError(new Communicator.ErrorInfo()
                {
                    isWarning = true, message = m
                });
            }

            // ERROR-ASSESSMENT
            if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id = Communicator.EXEPROG_PAR_CHECKED,
                message = $"Parameters checked and prepared", detailedInfo = ComposeOutputInfo()
            }))
            {
                return(false);
            }
            if (infoStore.communicator.errorCount > 0)
            {
                return(false);
            }

            // GENERATION OF HOUSEHOLD DATA (READING AND PREPARING VARIABLES)
            infoStore.hhAdmin = new HHAdmin(infoStore);
            infoStore.hhAdmin.GenerateData();

            // MAKE UPRATING FACTORS AVAILABLE AS GLOBAL VARIABLES, 2nd step: fill with values
            // NOTE: Uprating factors will have the default system year value. The DBYearVar is not supported in this case!
            // ( * T O D O * maybe we can support it with the use of conditional constants?)
            // * T O D O * this uprating factor logic shouldn't be here! especially as there is no warning if 1 is used
            foreach (var upFac in infoStore.country.upFacs)
            {
                if (upFac.Value.Get(infoStore.country.data.year, out double dui) && upFac.Value.Get(infoStore.country.sys.year, out double sui))
                {
                    infoStore.hhAdmin.GlobalSetVar(infoStore.operandAdmin.GetIndexInPersonVarList(upFac.Key.ToLower()), sui / dui);
                }
                else
                {
                    infoStore.hhAdmin.GlobalSetVar(infoStore.operandAdmin.GetIndexInPersonVarList(upFac.Key.ToLower()), 1);
                }
            }

            // MAKE INDIRECT TAXES AVAILABLE AS GLOBAL VARIABLES, 2nd step: fill with values
            foreach (var indTax in infoStore.country.indTaxes)
            {
                infoStore.hhAdmin.GlobalSetVar(infoStore.operandAdmin.GetIndexInPersonVarList(indTax.Key.ToLower()), indTax.Value);
            }

            foreach (FunBase fun in infoStore.spine.Values) // before reading data the HH.personVarList does not exist and var-parameters
            {
                fun.ReplaceVarNameByIndex();                // cannot know their index inthere, now the index is available and must be spread
            }
            // IF NECESSARY, CURRENCY CONVERSION
            // note: in terms of performance it seems to be irrelevant if this conversation is integrating into the reading data process
            // thus this "separate approach" seems appropriate as it is clearer
            if (infoStore.country.data.isEuro != infoStore.country.sys.areParamEuro)
            {
                double factor = infoStore.exRate;
                if (infoStore.country.sys.areParamEuro)
                {
                    factor = 1 / factor;
                }
                if (factor != 1)
                {
                    infoStore.hhAdmin.GlobalScaleFileReadVars(factor);
                }
            }

            // ERROR-ASSESSMENT
            if (infoStore.communicator.errorCount > 0)
            {
                return(false);
            }
            if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id = Communicator.EXEPROG_HH_READ,
                message = $"{infoStore.country.data.Name}: {infoStore.hhAdmin.hhs[0].personVarList[0].Count:N0} variables for {infoStore.hhAdmin.hhs.Count:N0} households ({infoStore.hhAdmin.hhs.Sum(x => x.GetPersonCount()):N0} individuals) read - largest household had {infoStore.hhAdmin.hhs.Max(x => x.GetPersonCount()):N0} members"
            }))
            {
                return(false);
            }

            // PREPARE THE SPINE (what to run before/after/in spine and, if necessary, looping)
            SpineAdmin spineManager  = new SpineAdmin(infoStore);
            bool       runSequential = infoStore.runConfig.forceSequentialRun || infoStore.runConfig.forceAutoSequentialRun;

            spineManager.PrepareSpine(ref runSequential);

            // ERROR-ASSESSMENT
            if (infoStore.communicator.errorCount > 0)
            {
                return(false);
            }
            if (!infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id = Communicator.EXEPROG_SPINE_DONE,
                message = $"Sequence of calculations prepared"
            }))
            {
                return(false);
            }

            // RUN THE WHOLE THING
            if (runSequential)
            {
                if (!RunSequential(spineManager))
                {
                    return(false);
                }
            }                                                                      // only if e.g. Totals is used or DefOutput is used in spine, ...
            else
            {
                RunParallel(spineManager);
            }
            infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id      = Communicator.EXEPROG_SPINE_DONE,
                message = $"Finished calculations"
            });

            // last ERROR-ASSESSMENT
            infoStore.communicator.ReportProgress(new Communicator.ProgressInfo()
            {
                id      = Communicator.EXEPROG_FINISHED,                                                                // hopefully with 0 warnings
                message = $"FINISHED with {infoStore.communicator.warningCount + infoStore.communicator.errorCount} errors/warnings"
            });

            return(true);
        }