EMMA Coverage Report (generated Sun Feb 05 10:43:15 CET 2012)
[all classes][de.uka.ipd.sdq.reliability.solver.pcm2markov]

COVERAGE SUMMARY FOR SOURCE FILE [MarkovTransformation.java]

nameclass, %method, %block, %line, %
MarkovTransformation.java0%   (0/1)0%   (0/14)0%   (0/657)0%   (0/172)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class MarkovTransformation0%   (0/1)0%   (0/14)0%   (0/657)0%   (0/172)
<static initializer> 0%   (0/1)0%   (0/5)0%   (0/3)
MarkovTransformation (): void 0%   (0/1)0%   (0/8)0%   (0/2)
createNextPermutation (List): boolean 0%   (0/1)0%   (0/107)0%   (0/20)
getEvaluationMode (PCMSolverWorkflowRunConfiguration): MarkovEvaluationType 0%   (0/1)0%   (0/4)0%   (0/2)
initializeResourceStates (List, int): void 0%   (0/1)0%   (0/30)0%   (0/6)
isStopConditionReached (PCMSolverWorkflowRunConfiguration, MarkovTransformati... 0%   (0/1)0%   (0/73)0%   (0/22)
runDSolver (UsageScenario, MarkovTransformationSource): void 0%   (0/1)0%   (0/36)0%   (0/9)
runPcm2Markov (PCMSolverWorkflowRunConfiguration, UsageScenario, MarkovTransf... 0%   (0/1)0%   (0/63)0%   (0/17)
runPcm2MarkovIteratively (PCMSolverWorkflowRunConfiguration, UsageScenario, M... 0%   (0/1)0%   (0/54)0%   (0/18)
runPcm2MarkovSingle (PCMSolverWorkflowRunConfiguration, UsageScenario, Markov... 0%   (0/1)0%   (0/97)0%   (0/27)
runScenarioTransform (PCMInstance, PCMSolverWorkflowRunConfiguration, UsageSc... 0%   (0/1)0%   (0/89)0%   (0/23)
runTransform (PCMInstance, PCMSolverWorkflowRunConfiguration, MarkovSensitivi... 0%   (0/1)0%   (0/13)0%   (0/3)
runTransformIteratively (PCMInstance, PCMSolverWorkflowRunConfiguration, Mark... 0%   (0/1)0%   (0/51)0%   (0/15)
runTransformSingle (PCMInstance, PCMSolverWorkflowRunConfiguration): List 0%   (0/1)0%   (0/27)0%   (0/6)

1package de.uka.ipd.sdq.reliability.solver.pcm2markov;
2 
3import java.util.ArrayList;
4import java.util.List;
5import java.util.concurrent.TimeUnit;
6 
7import org.apache.log4j.Logger;
8 
9import de.uka.ipd.sdq.markov.MarkovChain;
10import de.uka.ipd.sdq.pcm.usagemodel.UsageScenario;
11import de.uka.ipd.sdq.pcmsolver.models.PCMInstance;
12import de.uka.ipd.sdq.pcmsolver.runconfig.PCMSolverWorkflowRunConfiguration;
13import de.uka.ipd.sdq.pcmsolver.visitors.UsageModelVisitor;
14import de.uka.ipd.sdq.reliability.core.MarkovEvaluationType;
15import de.uka.ipd.sdq.reliability.core.MarkovFailureType;
16import de.uka.ipd.sdq.reliability.core.helper.MarkovFailureTypeHelper;
17import de.uka.ipd.sdq.reliability.solver.MarkovSolver;
18import de.uka.ipd.sdq.reliability.solver.sensitivity.MarkovSensitivity;
19 
20/**
21 * This class has the functionality to perform a complete PCM2Markov
22 * transformation.
23 * 
24 * @author brosch
25 * 
26 */
27public class MarkovTransformation {
28 
29        /**
30         * A logger to give detailed information about the PCM instance
31         * transformation.
32         */
33        private static Logger logger = Logger.getLogger(MarkovTransformation.class
34                        .getName());
35 
36        /**
37         * Provides functionality to manage failure types.
38         */
39        private MarkovFailureTypeHelper helper = new MarkovFailureTypeHelper();
40 
41        /**
42         * Checks whether there exists a next permutation based on the current one
43         * (which is given by the resources' current states). Generates the next
44         * permutation, if it exists, according to Narayana Pandita's method of
45         * systematically generating all permutations, and sets the resources'
46         * states accordingly.
47         * 
48         * @param descriptors
49         *            a list containing the processing resource descriptors
50         * @return true, if a new permutation was generated; false otherwise
51         */
52        private boolean createNextPermutation(
53                        final List<ProcessingResourceDescriptor> descriptors) {
54                int size = descriptors.size();
55 
56                int k = -1; // used for step 1 (see below)
57                int l; // used for step 2 (see below)
58                MarkovResourceState tmp; // used for step 4 (see below)
59                int left; // used for step 4 (see below)
60                int right; // used for step 4 (see below)
61 
62                /*
63                 * Systematic generation of all permutations according to Narayana
64                 * Pandita.
65                 */
66 
67                // 1. Find the largest index k such that a[k] < a[k + 1]. If no such
68                // index exists the permutation
69                // is the last permutation. [wikipedia]
70                for (int i = 0; i < size - 1; i++) {
71                        if (descriptors.get(i).getCurrentState() == MarkovResourceState.NA
72                                        && descriptors.get(i + 1).getCurrentState() == MarkovResourceState.OK) {
73                                k = i;
74                        }
75                }
76                if (k == -1) {
77                        return false; // no such index exists (i.e., there is no next
78                        // permutation) - return false
79                }
80 
81                // 2. Find the largest index l such that a[k] < a[l]. Since k + 1 is
82                // such an index, l is
83                // well defined and satisfies k < l. [wikipedia]
84                // Situation so far: a[k] = N/A, a[k+1] = OK.
85                // Now look for an l such that l > (k+1), and a[l] = OK.
86                l = k + 1; // a[k+1] is definitely OK. We may find another one, though:
87                for (int i = k + 2; i < size; i++) {
88                        if (descriptors.get(i).getCurrentState() == MarkovResourceState.OK) {
89                                l = i; // new such index found, update l
90                        }
91                }
92 
93                // 3. Swap a[k] with a[l] [wikipedia]
94                // I.e., switch their states.
95                descriptors.get(k).switchState();
96                descriptors.get(l).switchState();
97 
98                // 4. Reverse the sequence from a[k + 1] up to and including the final
99                // element a[n]. [wikipedia]
100                for (left = k + 1, right = size - 1; left < right; left++, right--) {
101                        // swap right and left
102                        tmp = descriptors.get(left).getCurrentState();
103                        descriptors.get(left).setCurrentState(
104                                        descriptors.get(right).getCurrentState());
105                        descriptors.get(right).setCurrentState(tmp);
106                }
107 
108                return true; // next permutation successfully generated - return true
109        }
110 
111        /**
112         * Retrieves the failure types evaluation mode from a given analysis
113         * configuration.
114         * 
115         * @param configuration
116         *            the analysis configuration
117         * @return the evaluation mode
118         */
119        private MarkovEvaluationType getEvaluationMode(
120                        PCMSolverWorkflowRunConfiguration configuration) {
121                return MarkovEvaluationType.valueOf(configuration
122                                .getMarkovEvaluationMode());
123        }
124 
125        /**
126         * Sets the leftmost <i>m</i> resources in the descriptors list to N/A.
127         * 
128         * @param descriptors
129         *            a list containing the processing resource descriptors
130         * @param m
131         *            the number of leftmost resources that will be set to N/A
132         */
133        private void initializeResourceStates(
134                        final List<ProcessingResourceDescriptor> descriptors, final int m) {
135                int size = descriptors.size();
136                // set m leftmost resources to N/A
137                for (int i = 0; i < m; i++) {
138                        descriptors.get(i).setCurrentState(MarkovResourceState.NA);
139                }
140                // set remaining (n-m) resources to OK
141                for (int i = m; i < size; i++) {
142                        descriptors.get(i).setCurrentState(MarkovResourceState.OK);
143                }
144        }
145 
146        /**
147         * Checks whether a termination condition - possibly specified by the user -
148         * holds.
149         * 
150         * @param configuration
151         *            configuration properties for the reliability solver workflow
152         * @param markovResult
153         *            results of the PCM2MarkovTransformation
154         * @param startTimeMs
155         *            indicates when the process started. This needs to be a value
156         *            in milliseconds determined by having called
157         *            System.currentTimeMillis().
158         * @return true, if a termination condition holds; false otherwise
159         */
160        private boolean isStopConditionReached(
161                        final PCMSolverWorkflowRunConfiguration configuration,
162                        final MarkovTransformationResult markovResult,
163                        final long startTimeMs) {
164 
165                // check solving time limit
166                if (configuration.isSolvingTimeLimitEnabled()) {
167                        // solving time limit reached?
168                        if (System.currentTimeMillis() - startTimeMs >= configuration
169                                        .getSolvingTimeLimit() * 1000) {
170                                // yes, stop condition holds - return true
171                                logger.info("Maximal solving time ("
172                                                + configuration.getSolvingTimeLimit()
173                                                + " seconds) reached - stopping!");
174                                return true;
175                        }
176                }
177 
178                // check limit for the number of evaluated physical system states
179                if (configuration.isNumberOfEvaluatedSystemStatesEnabled()) {
180                        // maximum number of system states to be evaluated reached?
181                        if (markovResult.getPhysicalStateEvaluationCount() == configuration
182                                        .getNumberOfEvaluatedSystemStates()) {
183                                // yes, stop condition holds - return true
184                                logger.info("Maximal number of evaluated system states ("
185                                                + configuration.getNumberOfEvaluatedSystemStates()
186                                                + ") reached - stopping!");
187                                return true;
188                        }
189                }
190 
191                // check limit for the number of exact decimal places
192                if (configuration.isNumberOfExactDecimalPlacesEnabled()) {
193                        // required number of exact decimal places reached?
194                        if (markovResult.hasRequiredAccuracy(configuration
195                                        .getNumberOfExactDecimalPlaces())) {
196                                // yes, stop condition holds - return true
197                                logger.info("Required number of exact decimal places ("
198                                                + configuration.getNumberOfExactDecimalPlaces()
199                                                + ") reached - stopping!");
200                                return true;
201                        }
202                }
203 
204                // no stop condition holds - return false
205                return false;
206        }
207 
208        /**
209         * Solves all parametric dependencies within a given PCM instance.
210         * 
211         * @param markovSource
212         *            state information required during the PCM2MarkovTransformation
213         * @param scenario
214         *            the usage scenario to evaluate
215         */
216        private void runDSolver(final UsageScenario scenario,
217                        final MarkovTransformationSource markovSource) {
218 
219                logger.debug("Resolving parametric dependencies.");
220                // Record the time consumed for solving parametric dependencies:
221                long startTime = System.nanoTime();
222 
223                // The parametric dependencies are solved by using a visitor:
224                UsageModelVisitor visitor = new UsageModelVisitor(markovSource
225                                .getModel());
226 
227                // Solve the PCM instance using the visitor:
228                visitor.doSwitch(scenario.getScenarioBehaviour_UsageScenario());
229 
230                // Let the user know about the time consumed:
231                long stopTime = System.nanoTime();
232                long duration = TimeUnit.NANOSECONDS.toMillis(stopTime - startTime);
233                logger.info("Solved parametric dependencies: " + duration + " ms");
234        }
235 
236        /**
237         * Transforms the given PCM instance into a Markov Chain instance. The PCM
238         * instance is assumed to have all parametric dependencies solved.
239         * 
240         * @param configuration
241         *            configuration properties for the reliability solver workflow
242         * @param scenario
243         *            the UsageScenario to evaluate
244         * @param markovSource
245         *            state information required during the PCM2MarkovTransformation
246         * @param markovResult
247         *            results of the PCM2MarkovTransformation
248         * @return true if the transformation has stopped before completion, such
249         *         that the result has to be approximated
250         */
251        private boolean runPcm2Markov(
252                        final PCMSolverWorkflowRunConfiguration configuration,
253                        final UsageScenario scenario,
254                        final MarkovTransformationSource markovSource,
255                        final MarkovTransformationResult markovResult) {
256 
257                logger.debug("Transforming PCM model into analysis model.");
258 
259                // Declare the result variable:
260                boolean approximate = false;
261 
262                // Record the time consumed for creating the Markov Chain instance:
263                long startTime = System.nanoTime();
264 
265                // Check for the requested state handling strategy:
266                if (!configuration.isIterationOverPhysicalSystemStatesEnabled()) {
267                        // Run a single Markov transformation according to the
268                        // "always ask" strategy:
269                        runPcm2MarkovSingle(configuration, scenario, markovSource,
270                                        markovResult);
271                } else {
272                        // Repeat the transformation for all physical system states:
273                        approximate = runPcm2MarkovIteratively(configuration, scenario,
274                                        markovSource, markovResult);
275                }
276 
277                // Let the user know about the time consumed:
278                long stopTime = System.nanoTime();
279                long duration = TimeUnit.NANOSECONDS.toMillis(stopTime - startTime);
280                logger.info("Finished Markov transformation: " + duration + " ms");
281                if (configuration.isIterationOverPhysicalSystemStatesEnabled()) {
282                        logger.info("Number of evaluated physical system states: "
283                                        + markovResult.getPhysicalStateEvaluationCount()
284                                        + " out of "
285                                        + markovResult.getNumberOfPhysicalSystemStates());
286                }
287 
288                // Return the result:
289                return approximate;
290        }
291 
292        /**
293         * Runs the PCM 2 Markov transformation for all possible classes, or until
294         * at least one of the possible termination conditions holds.
295         * 
296         * @param configuration
297         *            configuration properties for the reliability solver workflow
298         * @param scenario
299         *            the UsageScenario to evaluate
300         * @param markovSource
301         *            state information required during the PCM2MarkovTransformation
302         * @param markovResult
303         *            results of the PCM2MarkovTransformation
304         * @return true if the transformation has stopped before completion, such
305         *         that the result has to be approximated
306         */
307        private boolean runPcm2MarkovIteratively(
308                        final PCMSolverWorkflowRunConfiguration configuration,
309                        final UsageScenario scenario,
310                        final MarkovTransformationSource markovSource,
311                        final MarkovTransformationResult markovResult) {
312 
313                // Remember the start time of the transformation:
314                long startTimeMs = System.currentTimeMillis();
315 
316                // Some short notations:
317                List<ProcessingResourceDescriptor> descriptors = markovSource
318                                .getUnreliableResourceDescriptors();
319                final int size = descriptors.size();
320 
321                /*
322                 * Now, for all n resources: Let m out of n (for m in 0..(n-1))
323                 * resources fail in an iteration, considering first the resources that
324                 * are most likely to fail, moving on to resources that are less likely
325                 * to fail, finally considering the resources that are least likely to
326                 * fail.
327                 */
328                for (int permutationClass = 0; permutationClass <= size; permutationClass++) {
329 
330                        /*
331                         * Consider the current permutation first, where the m leftmost
332                         * resources are set to N/A, while the remaining ones are OK.
333                         */
334 
335                        // set m leftmost out of n resources total to N/A state
336                        initializeResourceStates(descriptors, permutationClass);
337 
338                        // markovSource.printCurrentResourceStates();
339 
340                        // Evaluate the physical system state:
341                        runPcm2MarkovSingle(configuration, scenario, markovSource,
342                                        markovResult);
343 
344                        // Check if any of the stop conditions is reached:
345                        if (isStopConditionReached(configuration, markovResult, startTimeMs)) {
346                                return true;
347                        }
348 
349                        /*
350                         * Now generate all other permutations where m out of n
351                         * ("n choose m") resources are N/A.
352                         */
353                        for (;;) {
354 
355                                // see if there's a next permutation
356                                if (!createNextPermutation(descriptors)) {
357                                        break; // there's no new permutation - leave inner for loop
358                                }
359 
360                                // markovSource.printCurrentResourceStates();
361 
362                                /*
363                                 * At this point, we have a new permutation, i.e. system state.
364                                 * It was generated via the createNextPermutation(descriptors)
365                                 * call above.
366                                 */
367 
368                                // Evaluate the physical system state:
369                                runPcm2MarkovSingle(configuration, scenario, markovSource,
370                                                markovResult);
371 
372                                // Check if any of the stop conditions is reached:
373                                if (isStopConditionReached(configuration, markovResult,
374                                                startTimeMs)) {
375                                        return true;
376                                }
377                        } // end for: generation of all "n choose m" permutations
378                } // end for: m out of n resources N/A
379 
380                // The whole transformation has been performed without reaching any stop
381                // conditions:
382                return false;
383        }
384 
385        /**
386         * Evaluates a single physical system state.
387         * 
388         * @param configuration
389         *            configuration properties for the reliability solver workflow
390         * @param scenario
391         *            the UsageScenario to evaluate
392         * @param markovSource
393         *            state information required during the PCM2MarkovTransformation
394         * @param markovResult
395         *            results of the PCM2MarkovTransformation
396         */
397        private void runPcm2MarkovSingle(
398                        final PCMSolverWorkflowRunConfiguration configuration,
399                        final UsageScenario scenario,
400                        final MarkovTransformationSource markovSource,
401                        final MarkovTransformationResult markovResult) {
402 
403                // Retrieve the MarkovSolver singleton instance:
404                MarkovSolver solver = MarkovSolver.getSingletonInstance();
405 
406                // If Markov statistics printing is switched on, do the first physical
407                // state evaluation without Markov chain reduction, and count the number
408                // of resulting Markov states:
409                boolean countStates = configuration.isPrintMarkovStatistics()
410                                && (markovResult.getPhysicalStateEvaluationCount() == 0);
411 
412                // calculate current state probability
413                double physicalStateProbability = 1.0;
414                if (configuration.isIterationOverPhysicalSystemStatesEnabled()) {
415                        for (ProcessingResourceDescriptor descriptor : markovSource
416                                        .getUnreliableResourceDescriptors()) {
417                                physicalStateProbability *= descriptor
418                                                .getStateProbability(descriptor.getCurrentState());
419                        }
420                }
421 
422                // The Markov Chain instance is created by using a visitor:
423                // MarkovUsageModelVisitor visitor = new MarkovUsageModelVisitor(
424                // markovSource, !countStates);
425                MarkovUsageModelVisitor visitor = new MarkovUsageModelVisitor(
426                                markovSource, getEvaluationMode(configuration), !configuration
427                                                .isIterationOverPhysicalSystemStatesEnabled(),
428                                configuration.isMarkovModelReductionEnabled(), configuration
429                                                .isMarkovModelTracesEnabled());
430 
431                // Create the Markov Chain instance using the visitor:
432                MarkovChain resultChain = (MarkovChain) visitor.doSwitch(scenario
433                                .getScenarioBehaviour_UsageScenario());
434 
435                // Display information to the user:
436                if (countStates) {
437                        logger
438                                        .info("Number of Markov states per evaluated physical system state:\t"
439                                                        + resultChain.getStates().size());
440                        logger
441                                        .info("Number of Markov transitions per evaluated physical system state:\t"
442                                                        + resultChain.getTransitions().size());
443                }
444 
445                // Solve the Markov Chain, and add the result to the overall success
446                // probability, weighted by the probability of the current resource
447                // state combination:
448                double[][] markovProbabilityMatrix = solver.solve(resultChain);
449 
450                // Add the results of this evaluation:
451                markovResult.addPhysicalStateResults(resultChain,
452                                markovProbabilityMatrix, physicalStateProbability);
453        }
454 
455        /**
456         * Transforms a PCM instance into a Markov Chain instance.
457         * 
458         * The transformation is performed in two steps. In the first step,
459         * parametric dependencies within the PCM instance are solved using the
460         * dependency solver. The resulting PCM instance is then transformed into a
461         * Markov Chain.
462         * 
463         * @param model
464         *            the input PCM instance
465         * @param configuration
466         *            configuration properties for the reliability solver workflow
467         * @param scenario
468         *            the usage scenario to transform
469         * @return the transformation results
470         */
471        private MarkovTransformationResult runScenarioTransform(
472                        final PCMInstance model,
473                        final PCMSolverWorkflowRunConfiguration configuration,
474                        final UsageScenario scenario) {
475 
476                // Initialize failure type information:
477                List<MarkovFailureType> failureTypes = helper.getFailureTypes(
478                                getEvaluationMode(configuration), model.getRepositories(),
479                                model.getResourceEnvironment(), model.getSystem());
480 
481                // Initialize state information:
482                MarkovTransformationSource markovSource = new MarkovTransformationSource(
483                                model, true);
484                MarkovTransformationResult markovResult = new MarkovTransformationResult(
485                                configuration, markovSource, scenario, failureTypes);
486                boolean approximate = false;
487 
488                // As a first step, solve parametric dependencies of the PCM instance:
489                try {
490                        runDSolver(scenario, markovSource);
491                } catch (Exception e) {
492 
493                        // The parametric dependencies could not be solved:
494                        logger
495                                        .error("Solving of parametric dependencies caused exception: "
496                                                        + e.getMessage() + " [" + e.getClass() + "]");
497                        e.printStackTrace();
498 
499                        return null;
500                }
501 
502                // Second, the PCM instance is transformed into a Markov Chain instance
503                // and solved for determining system reliability:
504                try {
505                        approximate = runPcm2Markov(configuration, scenario, markovSource,
506                                        markovResult);
507                } catch (Exception e) {
508                        logger.error("PCM 2 Markov transformation caused exception: "
509                                        + e.getMessage() + " [" + e.getClass() + "]");
510                        e.printStackTrace();
511                }
512 
513                markovResult.setApproximate(approximate);
514 
515                // Return the transformation results:
516                return markovResult;
517        }
518 
519        /**
520         * Performs a PCM2Markov transformation.
521         * 
522         * @param model
523         *            the input PCM instance
524         * @param configuration
525         *            configuration properties for the reliability solver workflow
526         * @param sensitivity
527         *            sensitivity analysis configuration
528         * @return the transformation results
529         */
530        public List<MarkovTransformationResult> runTransform(
531                        final PCMInstance model,
532                        final PCMSolverWorkflowRunConfiguration configuration,
533                        final MarkovSensitivity sensitivity) {
534                if (sensitivity != null) {
535                        return runTransformIteratively(model, configuration, sensitivity);
536                } else {
537                        return runTransformSingle(model, configuration);
538                }
539        }
540 
541        /**
542         * Transforms a PCM instance into a Markov Chain instance.
543         * 
544         * The transformation is repeated to enable sensitivity analysis.
545         * 
546         * @param model
547         *            the PCM instance
548         * @param configuration
549         *            configuration properties for the reliability solver workflow
550         * @param sensitivity
551         *            sensitivity analysis configuration
552         * @return the transformation results (only the results of the last step in
553         *         the sensitivity analysis are returned)
554         */
555        private List<MarkovTransformationResult> runTransformIteratively(
556                        final PCMInstance model,
557                        final PCMSolverWorkflowRunConfiguration configuration,
558                        final MarkovSensitivity sensitivity) {
559                int sensitivityStepCount = 0;
560                List<MarkovTransformationResult> markovResults = null;
561                sensitivity.initialize(model);
562                PCMInstance step = sensitivity.getNextModel();
563                while (step != null) {
564                        sensitivityStepCount++;
565                        logger.info("Starting sensitivity analysis step "
566                                        + sensitivityStepCount + "...");
567                        markovResults = runTransformSingle(step, configuration);
568                        sensitivity.logResults(markovResults);
569                        logger.info("Sensitivity analysis step " + sensitivityStepCount
570                                        + " completed");
571                        step = sensitivity.getNextModel();
572                }
573                sensitivity.finalize();
574                return markovResults;
575        }
576 
577        /**
578         * Transforms a PCM instance into a Markov Chain instance.
579         * 
580         * The transformation is performed in two steps. In the first step,
581         * parametric dependencies within the PCM instance are solved using the
582         * dependency solver. The resulting PCM instance is then transformed into a
583         * Markov Chain.
584         * 
585         * @param model
586         *            the input PCM instance
587         * @param configuration
588         *            configuration properties for the reliability solver workflow
589         * @return the transformation results
590         */
591        private List<MarkovTransformationResult> runTransformSingle(
592                        final PCMInstance model,
593                        final PCMSolverWorkflowRunConfiguration configuration) {
594 
595                // Declare result list:
596                ArrayList<MarkovTransformationResult> resultList = new ArrayList<MarkovTransformationResult>();
597 
598                // Transform all usage scenarios individually:
599                for (UsageScenario scenario : model.getUsageModel()
600                                .getUsageScenario_UsageModel()) {
601                        resultList
602                                        .add(runScenarioTransform(model, configuration, scenario));
603                }
604 
605                // Return all transformation results:
606                return resultList;
607        }
608}

[all classes][de.uka.ipd.sdq.reliability.solver.pcm2markov]
EMMA 2.0.9414 (unsupported private build) (C) Vladimir Roubtsov