/// <summary> /// Generate sensitivities from the results of <see cref="PerturbAndObserveRunner{T}.RunPerturbAndObserve"/>, /// and using the selection functions <see cref="ResultSelector"/> and <see cref="RecordedPerturbationSelector"/>. /// </summary> /// <param name="perturbAndObserveRunner">The <see cref="PerturbAndObserveRunner{T}"/> to determine sensitivities /// from.</param> /// <returns>A twin-key dictionary with each value representing the sensitivity of the specified value at the Y-element /// to changes in the perturbed value at the X-element.</returns> public TwinKeyDictionary<String, String, double> GenerateSensitivities(PerturbAndObserveRunner<T> perturbAndObserveRunner) { var results = new TwinKeyDictionary<String, String, double>(); foreach (var perturbation in perturbAndObserveRunner.AfterValues) { foreach (var observation in perturbation.Value) { T oldResult; if (!perturbAndObserveRunner.BeforeValues.TryGetValue(observation.Key, out oldResult)) continue; var sensitivity = (Convert.ToDouble(ResultSelector(observation.Value)) - Convert.ToDouble(ResultSelector(oldResult)))/Convert.ToDouble(RecordedPerturbationSelector(perturbation.Key.Item2)); results.Add(perturbation.Key.Item1.Item1, observation.Key.Item1, sensitivity); } } return results; }
/// <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)); }