public void TestSensitivityGenerator()
        {
            PerturbAndObserveRunner<Complex> runner = new PerturbAndObserveRunner<Complex>(new OpenDSSSimulator());
            runner.NetworkFilename = AppDomain.CurrentDomain.BaseDirectory + @"\TestNetworks\Ex9_5_nogen.dss";
            runner.PerturbCommands = new String[] { "new Generator.{0} bus1={1} phases=3 model=1 status=fixed kV={2} Vminpu=0.9 Vmaxpu=1.1 kW={3} kvAR=0" };
            runner.PerturbElementSelector = network => network.Buses.Values;
            runner.PerturbElementValuesSelector = elem => { Bus b = (Bus)elem; return new Object[] { "gg-" + b.ID, b.ID, b.BaseVoltage * Math.Sqrt(3) / 1000, 3260 }; };
            runner.PerturbValuesToRecord = x => x[3];
            runner.ObserveElementSelector = network => network.Buses.Values;
            runner.ObserveElementValuesSelector = elem => ((Bus)elem).Voltage;
            runner.RunPerturbAndObserve();

            SensitivityGenerator<Complex> generator = new SensitivityGenerator<Complex>();
            generator.RecordedPerturbationSelector = x => x;
            generator.ResultSelector = x => x.Magnitude;
            var sensitivities = generator.GenerateSensitivities(runner);

            AssertEqualByPercent(0.018510852, sensitivities.MapX["b2"]["b2"], 0.1);
            AssertEqualByPercent(0.007367939, sensitivities.MapX["b2"]["b3"], 0.1);
            AssertEqualByPercent(0.015043472, sensitivities.MapX["b2"]["b4"], 0.1);
            AssertEqualByPercent(0.006543352, sensitivities.MapX["b3"]["b2"], 0.1);
            AssertEqualByPercent(0.013438371, sensitivities.MapX["b3"]["b3"], 0.1);
            AssertEqualByPercent(0.009204037, sensitivities.MapX["b3"]["b4"], 0.1);
            AssertEqualByPercent(0.018171524, sensitivities.MapX["b4"]["b2"], 0.1);
            AssertEqualByPercent(0.012616745, sensitivities.MapX["b4"]["b3"], 0.1);
            AssertEqualByPercent(0.025445765, sensitivities.MapX["b4"]["b4"], 0.1);
        }
        public void TestSensitivityGenerator()
        {
            PerturbAndObserveRunner <Complex> runner = new PerturbAndObserveRunner <Complex>(new OpenDSSSimulator());

            runner.NetworkFilename              = AppDomain.CurrentDomain.BaseDirectory + @"\TestNetworks\Ex9_5_nogen.dss";
            runner.PerturbCommands              = new String[] { "new Generator.{0} bus1={1} phases=3 model=1 status=fixed kV={2} Vminpu=0.9 Vmaxpu=1.1 kW={3} kvAR=0" };
            runner.PerturbElementSelector       = network => network.Buses.Values;
            runner.PerturbElementValuesSelector = elem => { Bus b = (Bus)elem; return(new Object[] { "gg-" + b.ID, b.ID, b.BaseVoltage *Math.Sqrt(3) / 1000, 3260 }); };
            runner.PerturbValuesToRecord        = x => x[3];
            runner.ObserveElementSelector       = network => network.Buses.Values;
            runner.ObserveElementValuesSelector = elem => ((Bus)elem).Voltage;
            runner.RunPerturbAndObserve();

            SensitivityGenerator <Complex> generator = new SensitivityGenerator <Complex>();

            generator.RecordedPerturbationSelector = x => x;
            generator.ResultSelector = x => x.Magnitude;
            var sensitivities = generator.GenerateSensitivities(runner);

            AssertEqualByPercent(0.018510852, sensitivities.MapX["b2"]["b2"], 0.1);
            AssertEqualByPercent(0.007367939, sensitivities.MapX["b2"]["b3"], 0.1);
            AssertEqualByPercent(0.015043472, sensitivities.MapX["b2"]["b4"], 0.1);
            AssertEqualByPercent(0.006543352, sensitivities.MapX["b3"]["b2"], 0.1);
            AssertEqualByPercent(0.013438371, sensitivities.MapX["b3"]["b3"], 0.1);
            AssertEqualByPercent(0.009204037, sensitivities.MapX["b3"]["b4"], 0.1);
            AssertEqualByPercent(0.018171524, sensitivities.MapX["b4"]["b2"], 0.1);
            AssertEqualByPercent(0.012616745, sensitivities.MapX["b4"]["b3"], 0.1);
            AssertEqualByPercent(0.025445765, sensitivities.MapX["b4"]["b4"], 0.1);
        }
        /// <summary>
        /// Generates Voltage Sensitivities to changes in P and Q at each load bus on
        /// the network.
        /// </summary>
        /// <remarks>
        /// This is a convenience class that uses the functionality supplied by
        /// <see cref="SensitivityGenerator{T}"/> and <see cref="PerturbAndObserveRunner{T}"/>.
        /// For other kinds of sensitivities, you may wish to use these classes directly.</remarks>
        /// <param name="Simulator">The simulator to use for the generation of sensitivities.</param>
        /// <param name="NetworkMasterFile">The file path of the Network to calculate sensitivities for.</param>
        /// <param name="CommandString">A command for issuing perturbations. The command should be
        /// compatible with <see cref="String.Format(String,Object[])"/>-style format strings, and should
        /// use <c>{0}</c> to represent a random ID, <c>{1}</c> to represent the bus that perturbation should occur
        /// on, <c>{2}</c> to represent a kW quantity to perturb by and <c>{3}</c> to represent a kVAr quantity to
        /// perturb by.
        /// <example>
        /// As an example, the following string specifies a new generator for perturbation in OpenDSS syntax:
        /// <code>
        /// "new Generator.{0} bus1={1} phases=3 model=1 status=fixed kV=11 Vminpu=0.9 Vmaxpu=1.1 kW={2} kvAR={3}"</code></example></param>
        /// <param name="PerturbationFrac">The fraction of average load size to perturb by.</param>
        /// <returns>A 2-axis dictionary, in which the X-axis represents the source bus, the Y-axis represents the affected bus,
        /// and the values are an index of sensitivity information.</returns>
        public static TwinKeyDictionary<String, String, VoltageSensitivityToPQDataSet> GetVoltageSensitivityToComplexPower(ISimulator Simulator, String NetworkMasterFile, String CommandString, double PerturbationFrac)
        {
            PerturbAndObserveRunner<Complex> perturbAndObserve = new PerturbAndObserveRunner<Complex>(Simulator);
            NetworkController controller = new NetworkController(Simulator);
            controller.NetworkFilename = NetworkMasterFile;
            controller.Execute();
            var avgLoad = controller.Network.Loads.Select(load=>load.ActualKVA).Aggregate((seed,elem) => seed+elem);
            avgLoad /= controller.Network.Loads.Count;
            perturbAndObserve.NetworkFilename = NetworkMasterFile;
            perturbAndObserve.ObserveElementSelector = network => network.Buses.Values.Where(bus => bus.ConnectedTo.OfType<Load>().Any());
            perturbAndObserve.PerturbElementSelector = perturbAndObserve.ObserveElementSelector;
            perturbAndObserve.PerturbCommands = new []{CommandString};
            perturbAndObserve.ObserveElementValuesSelector = elem => ((Bus)elem).Voltage;

            SensitivityGenerator<Complex> generator = new SensitivityGenerator<Complex>();

            //real
            perturbAndObserve.PerturbElementValuesSelector = bus => new Object[] {"inject-"+bus.ID,bus.ID,PerturbationFrac * avgLoad.Real, 0};
            perturbAndObserve.PerturbValuesToRecord = vars => vars[2];
            perturbAndObserve.RunPerturbAndObserve();

            generator.RecordedPerturbationSelector = x => x;
            generator.ResultSelector = x => x.Magnitude;

            var MagnitudeDictionaryReal = generator.GenerateSensitivities(perturbAndObserve);

            generator.ResultSelector = x => x.Phase;
            var PhaseDictionaryReal = generator.GenerateSensitivities(perturbAndObserve);

            //imaginary
            perturbAndObserve.PerturbElementValuesSelector = bus => new Object[] { "inject-" + bus.ID, bus.ID, 0, PerturbationFrac * avgLoad.Imaginary };
            perturbAndObserve.PerturbValuesToRecord = vars => vars[3];
            perturbAndObserve.RunPerturbAndObserve();

            generator.RecordedPerturbationSelector = x => x;
            generator.ResultSelector = x => x.Magnitude;

            var MagnitudeDictionaryImag = generator.GenerateSensitivities(perturbAndObserve);

            generator.ResultSelector = x => x.Phase;
            var PhaseDictionaryImag = generator.GenerateSensitivities(perturbAndObserve);

            // now merge all the dictionaries.
            return TwinKeyDictionaryMerge(MagnitudeDictionaryReal, MagnitudeDictionaryImag, PhaseDictionaryReal, PhaseDictionaryImag);
        }