I don't like this code, it is overcomplicated and impractical, so I'm looking to simplify it.
I want it to change a var by a random amount, and I need to put at least 150 variables into this code.
//Variable list
public double price1 = 100;
public double price2 = 100;
public double price3 = 100;
public void DaysEnd(){ //Simplified version of inefficient code
var = price1;
HVariation();
price1 = newvar;
var = price2;
HVariation();
price2 = newvar;
var = price2;
MVariation();
price2 = newvar;
var = price3;
LVariation();
price3 = newvar;
}
public void Hvariation(){
newvar = var + (var * (Random.NextDouble(0 - 0.5, 0.5)));
}
public void Mvariation(){
newvar = var + (var * (Random.NextDouble(0 - 0.25, 0.25)));
}
public void Lvariation(){
newvar = var + (var * (Random.NextDouble(0 - 0.1, 0.5)));
}
This should get you started
List<double> values = new List<double> { 100, 100, 200, 500, ... };
values = values.Select(val => Hvariation(val)).ToList();
// now all values have been altered by Hvariation
...
private readonly Random _rand = new Random();
public double Hvariation(double val) {
return val + (val * (_rand.NextDouble(-0.5, 0.5)));
}
The first thing to do is find repeated code. For example:
var = price3;
LVariation(); //Different variations
price3 = newvar;
This can be turned into a method (that takes the variation as a parameter).
To do this, you will also need to make a default variation that takes the min and max:
public void Variation(double min, double max){
newvar = var + (var * (Random.NextDouble(min, max)));
}
You can then put this together to reduce code to look some thing like this:
public double UpdatePrice(double price, double min, double max)
{
var = price;
Variation(min, max);
return newvar;
}
In general, if I have to copy the code more than once (or even once if the amount copied is significant), I turn the code into a method.
You can simplify this by instead of defining three variation methods, defining a variation level and passing it into a single method. I'm not sure if you would need it to be in arrays or if you can use lists (in which case lists are preferable), but you can store your variable in an array instead of defining a variable name for each one and separate them into logical groupings as you need to. You can then apply the change/transformation to each array using LINQ. An example of this would be
public enum VariationLevel
{
High,
Medium,
Low
};
public double[] HighVariancePrices =
{
100, 100, 100, 100, 100
};
public double[] MediumVariancePrices =
{
100, 100, 100, 100, 100
};
public double[] LowVariancePrices =
{
100, 100, 100, 100, 100
};
public void DaysEnd()
{
HighVariancePrices = HighVariancePrices.Select(price => GetVariation(price, VariationLevel.High)).ToArray();
MediumVariancePrices = MediumVariancePrices.Select(price => GetVariation(price, VariationLevel.Medium)).ToArray();
LowVariancePrices = LowVariancePrices.Select(price => GetVariation(price, VariationLevel.Low)).ToArray();
}
public double GetVariation(double value, VariationLevel variationLevel)
{
switch (variationLevel)
{
case VariationLevel.High:
return value + (value * (Random.NextDouble(0 - 0.5, 0.5)));
case VariationLevel.Medium:
return value + (value * (Random.NextDouble(0 - 0.25, 0.25)));
case VariationLevel.Low:
return value + (value * (Random.NextDouble(0 - 0.1, 0.5)));
}
}
However, the code around Random.NextDouble() doesn't compile (because NextDouble doesn't take arguments) so I'm not certain what you're trying to do there, but that's outside of the scope of "how can I simplify my code?" Hope this helps some.
Related
I am trying to reduce the computing time using the multi-thread technique. My problem is to calculate the score of independent items. For example, I have 10 items and need to get the score for each item. I can use the single thread ten times serially for getting the score or the multi-thread parallelly.
In my code, the computing time was just reduced about average 7% when I used a multi-thread code. I expected multi-thread code would reduce it over about 50%.
I also changed the power plan (Control Panel > Hardware and Sound > Power Options) from Balanced to High performance for it.
I solved the same problem using four options.
Single Thread and Balanced Option : 11min
Multi Thread and Balanced Option : 10min 40Sec (20sec decreased)
Single Thread and High Performance : 10 min
Multi Thread and High Performance : 10min 10sec.(10sec increased)
My questions are
What is wrong in my code as below?
Why does the computing time of multi-thread code increase compared to that of single-thread code when the power option sets the high performance (3 and 4 case as above)
How many core do I need for improving computing time? the more is the better??
I am a beginner in the multi-thread world in C#. Please guide me to go right.
Thank you for your kind answer in advance.
I made a simple example for explaining our code structure. The code creates 10000 items and gets the score of them. Also we use the "lock" for parallel computing of List structure.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleCode
{
class Program
{
static List<Item> Storage = new List<Item>();
static object key = new object();
static void Main(string[] args)
{
List<Item> items = CreateItems(10000);
int maxDegreeOfParallelism = 4;
Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
(item) => Evaluate(item));
}
public static void Evaluate(Item item)
{
item.Score = 0;
item.Score += GetScore1(item);
item.Score += GetScore2(item);
}
public static double GetScore1(Item item)
{
return item.UnitQty * 100;
}
public static double GetScore2(Item item)
{
lock (key)
{
if (item.UnitQty % 2 == 0)
Storage.Add(item);
else if (Storage.Count > 0)
Storage.RemoveAt(Storage.Count - 1);
return item.UnitQty * Storage.Count;
}
}
public static List<Item> CreateItems(int count)
{
List<Item> items = new List<Item>();
for(int i=0; i<count; i++)
{
items.Add(CreateItem(i));
}
return items;
}
public static Item CreateItem(int index)
{
Item item = new Item();
item.UnitQty = index % 25 + 1;
return item;
}
internal class Item
{
public double Score;
public int UnitQty;
}
}
}
You have 4 items doing a tiny calculation then waiting in line to be able to add or remove a result from your container.
Parallelism is better used for compute heavy tasks, right now I would estimate the start up/stop overhead is almost equal to the computation work its doing. Add locking into the equation and more of the time savings being eaten into.
Based off of your comment:
We assume item_1 has priroity_score =2, location_score = 3, delay_score =2; item_2 has priroity_score =4, location_score = 3, delay_score =1; item_3 has priroity_score = 5, location_score = 2, delay_score =3 and priority_weight = 30, location_weight = 40, delay_weight = 50. The scores of items are item_1 = 230 + 340 + 250 = 280, item_2 = 4 30 + 340+ 150 = 290, item_3 = 5 * 30 + 2 * 40 + 3 * 50 = 380
using System.Collections.Generic;
using System.Linq;
namespace SampleCode
{
/*
* We assume
* item_1 has priroity_score =2, location_score = 3, delay_score =2;
* item_2 has priroity_score =4, location_score = 3, delay_score =1;
* item_3 has priroity_score = 5, location_score = 2, delay_score =3
*
* and priority_weight = 30, location_weight = 40, delay_weight = 50.
* The scores of items are item_1 = 2*30 + 3*40 + 2*50 = 280, item_2 = 4 *30 + 3*40+ 1*50 = 290, item_3 = 5 * 30 + 2 * 40 + 3 * 50 = 38
* */
class Program
{
static List<Item> Storage = new List<Item>();
static object key = new object();
static void Main(string[] args)
{
var range = Enumerable.Range(0, 10000);
var maxDegreeOfParallelism = 4;
var priorityWeight = 30;
var locationWeight = 40;
var delayWeight = 50;
var items = range
.AsParallel()
.WithDegreeOfParallelism(maxDegreeOfParallelism)
.Select(i =>
{
var item = new Item { PriorityScore = i + 2, LocationScore = i + 3, DelayScore = i + 2 };
item.Score = Evaluate(item, priorityWeight, locationWeight, delayWeight);
return item;
});
Storage.AddRange(items);
}
private static double Evaluate(Item item, int priorityWeight, int locationWeight, int delayWeight)
{
return item.PriorityScore * priorityWeight + item.LocationScore * locationWeight + item.DelayScore * delayWeight;
}
internal class Item
{
public double Score;
public double PriorityScore;
public double LocationScore;
public double DelayScore;
}
}
}
But even here, running with PLinq takes ~30ms in debug where removing multithreading will take ~2ms because of the threading overhead vs the light weight operations you are performing. Also note that memory is a shared resource and is not CPU bound, so heap allocations will present an area of contention. (Thread contention when allocating memory)
In your example code the contention is created by the lock in GetScore2.
If your final code has more complex operations or if you are processing a very large number of inputs. As long as you avoid locking, using plinq might give you better improvements than shown here.
You can also use Parallel.ForEach if you have pre-allocated values:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SampleCode
{
/*
* We assume
* item_1 has priroity_score =2, location_score = 3, delay_score =2;
* item_2 has priroity_score =4, location_score = 3, delay_score =1;
* item_3 has priroity_score = 5, location_score = 2, delay_score =3
*
* and priority_weight = 30, location_weight = 40, delay_weight = 50.
* The scores of items are item_1 = 2*30 + 3*40 + 2*50 = 280, item_2 = 4 *30 + 3*40+ 1*50 = 290, item_3 = 5 * 30 + 2 * 40 + 3 * 50 = 38
* */
class Program
{
static List<Item> Storage = new List<Item>();
static void Main(string[] args)
{
// Bumped n to see results vvvvvvvv
var range = Enumerable.Range(0, 10000000);
var maxDegreeOfParallelism = 4;
var priorityWeight = 30;
var locationWeight = 40;
var delayWeight = 50;
var items = range
.Select(i => new Item { PriorityScore = i + 2, LocationScore = i + 3, DelayScore = i + 2 })
.ToList();
Parallel.ForEach(items,
new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism },
i => i.Score = Evaluate(i, priorityWeight, locationWeight, delayWeight));
//~43ms
foreach(var i in items)
{
i.Score = Evaluate(i, priorityWeight, locationWeight, delayWeight);
}
//~99ms
Storage.AddRange(items);
}
private static double Evaluate(Item item, int priorityWeight, int locationWeight, int delayWeight)
{
return item.PriorityScore * priorityWeight + item.LocationScore * locationWeight + item.DelayScore * delayWeight;
}
internal class Item
{
public double Score;
public double PriorityScore;
public double LocationScore;
public double DelayScore;
}
}
}
As always with concurrency, there are tradeoffs and you will need to benchmark for your specific needs.
Multithreading can take notably significant time to switch between thread contexts, so it is not obliged to give some speed-up. Sometimes everything happens the other way around.
I have converted this Java program into a C# program.
using System;
using System.Collections.Generic;
namespace RandomNumberWith_Distribution__Test
{
public class DistributedRandomNumberGenerator
{
private Dictionary<Int32, Double> distribution;
private double distSum;
public DistributedRandomNumberGenerator()
{
distribution = new Dictionary<Int32, Double>();
}
public void addNumber(int val, double dist)
{
distribution.Add(val, dist);// are these two
distSum += dist; // lines correctly translated?
}
public int getDistributedRandomNumber()
{
double rand = new Random().NextDouble();//generate a double random number
double ratio = 1.0f / distSum;//why is ratio needed?
double tempDist = 0;
foreach (Int32 i in distribution.Keys)
{
tempDist += distribution[i];
if (rand / ratio <= tempDist)//what does "rand/ratio" signify? What does this comparison achieve?
{
return i;
}
}
return 0;
}
}
public class MainClass
{
public static void Main(String[] args)
{
DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
drng.addNumber(2, 0.3d);
drng.addNumber(3, 0.5d);
//=================
// start simulation
int testCount = 1000000;
Dictionary<Int32, Double> test = new Dictionary<Int32, Double>();
for (int i = 0; i < testCount; i++)
{
int random = drng.getDistributedRandomNumber();
if (test.ContainsKey(random))
{
double prob = test[random]; // are these
prob = prob + 1.0 / testCount;// three lines
test[random] = prob; // correctly translated?
}
else
{
test.Add(random, 1.0 / testCount);// is this line correctly translated?
}
}
foreach (var item in test.Keys)
{
Console.WriteLine($"{item}, {test[item]}");
}
Console.ReadLine();
}
}
}
I have several questions:
Can you check if the marked-by-comment lines are correct or need explanation?
Why doesn't getDistributedRandomNumber() check if the sum of the distribution 1 before proceeding to further calculations?
The method
public void addNumber(int val, double dist)
Is not correctly translated, since you are missing the following lines:
if (this.distribution.get(value) != null) {
distSum -= this.distribution.get(value);
}
Those lines should cover the case when you call the following (based on your example code):
DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
drng.addNumber(1, 0.5d);
So calling the method addNumber twice with the same first argument, the missing code part looks if the first argument is already present in the dictionary and if so it will remove the "old" value from the dictionary to insert the new value.
Your method should look like this:
public void addNumber(int val, double dist)
{
if (distribution.TryGetValue(val, out var oldDist)) //get the old "dist" value, based on the "val"
{
distribution.Remove(val); //remove the old entry
distSum -= oldDist; //substract "distSum" with the old "dist" value
}
distribution.Add(val, dist); //add the "val" with the current "dist" value to the dictionary
distSum += dist; //add the current "dist" value to "distSum"
}
Now to your second method
public int getDistributedRandomNumber()
Instead of calling initializing a new instance of Random every time this method is called you should only initialize it once, so change the line
double rand = new Random().NextDouble();
to this
double rand = _random.NextDouble();
and initialize the field _random outside of a method inside the class declaration like this
public class DistributedRandomNumberGenerator
{
private Dictionary<Int32, Double> distribution;
private double distSum;
private Random _random = new Random();
... rest of your code
}
This will prevent new Random().NextDouble() from producing the same number over and over again if called in a loop.
You can read about this problem here: Random number generator only generating one random number
As I side note, fields in c# are named with a prefix underscore. You should consider renaming distribution to _distribution, same applies for distSum.
Next:
double ratio = 1.0f / distSum;//why is ratio needed?
Ratio is need because the method tries its best to do its job with the information you have provided, imagine you only call this:
DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
int random = drng.getDistributedRandomNumber();
With those lines you told the class you want to have the number 1 in 20% of the cases, but what about the other 80%?
And that's where the ratio variable comes in place, it calculates a comparable value based on the sum of probabilities you have given.
eg.
double ratio = 1.0f / distSum;
As with the latest example drng.addNumber(1, 0.2d); distSum will be 0.2, which translates to a probability of 20%.
double ratio = 1.0f / 0.2;
The ratio is 5.0, with a probability of 20% the ratio is 5 because 100% / 5 = 20%.
Now let's have a look at how the code reacts when the ratio is 5
double tempDist = 0;
foreach (Int32 i in distribution.Keys)
{
tempDist += distribution[i];
if (rand / ratio <= tempDist)
{
return i;
}
}
rand will be to any given time a value that is greater than or equal to 0.0, and less than 1.0., that's how NextDouble works, so let's assume the following 0.254557522132321 as rand.
Now let's look what happens step by step
double tempDist = 0; //initialize with 0
foreach (Int32 i in distribution.Keys) //step through the added probabilities
{
tempDist += distribution[i]; //get the probabilities and add it to a temporary probability sum
//as a reminder
//rand = 0.254557522132321
//ratio = 5
//rand / ratio = 0,0509115044264642
//tempDist = 0,2
// if will result in true
if (rand / ratio <= tempDist)
{
return i;
}
}
If we didn't apply the ratio the if would be false, but that would be wrong, since we only have a single value inside our dictionary, so no matter what the rand value might be the if statement should return true and that's the natur of rand / ratio.
To "fix" the randomly generated number based on the sum of probabilities we added. The rand / ratio will only be usefull if you didn't provide probabilites that perfectly sum up to 1 = 100%.
eg. if your example would be this
DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
drng.addNumber(2, 0.3d);
drng.addNumber(3, 0.5d);
You can see that the provided probabilities equal to 1 => 0.2 + 0.3 + 0.5, in this case the line
if (rand / ratio <= tempDist)
Would look like this
if (rand / 1 <= tempDist)
Divding by 1 will never change the value and rand / 1 = rand, so the only use case for this devision are cases where you didn't provided a perfect 100% probability, could be either more or less.
As a side note, I would suggest changing your code to this
//call the dictionary distributions (notice the plural)
//dont use .Keys
//var distribution will be a KeyValuePair
foreach (var distribution in distributions)
{
//access the .Value member of the KeyValuePair
tempDist += distribution.Value;
if (rand / ratio <= tempDist)
{
return i;
}
}
Your test routine seems to be correctly translated.
I'm trying Linq over Imperative style, but I can't convert this conditional inside Aggregate to Linq.
Consider two following examples.
Simple example:
public enum Color {
Red, // Red score = 10
Yellow, // Yellow score = 5
Green, // Green score = 2
}
//Populate our sample list
public List<Color> Colors = new List<Color> {Red, Green, Green, Yellow};
//I need help on this one
public float Score => Colors.Aggregate(0.0f, (total, next) =>
{
//How to properly use conditional inside Aggregate?
if (next == Color.Red) {
return total + 10.0f;
} else if (next == Color.Yellow) {
return total + 5.0f;
} else if (next == Color.Green) {
return total + 2.0f;
}
//edit: forgot the default
return total;
}
Log(Score); //19
Edit: I have tried moving the conditional to Select, but then it will just move the problem, Which is how to add conditional inside Linq Select?
public float Score => Colors.Select(x =>
{
// The problem still happening
if (x == Color.Red) {
return 10.0f;
} else if (x == Color.Yellow) {
return 5.0f;
} else if (x == Color.Green) {
return 2.0f;
}
return 0.0f;
}
.Aggregate(0.0f, (total, next) => total + next);
And here is the complex example, basically it's just a stat modifier for a game,
// This is a Game Status Modifier, for example: "Strength 30 + 10%"
public enum StatModType
{
Flat = 100, // Flat addition to Stat
PercentAdd = 200, // Percent addition to Stat
... // many other type of addition
}
private float _baseValue = 30.0f;
public List<StatModifier> StatModifiers = new List<StatModifier>
{...} //dummy data
public float Value => StatModifiers.Aggregate(_baseValue, (finalValue, mod) =>
{
//I need help on this one
if (mod.Type == StatModType.Flat)
return finalValue + mod.Value;
else if (mod.Type == StatModType.PercentAdd)
// When we encounter a "PercentAdd" modifier
return finalValue + finalValue * mod.Value;
else if (mod.Type == ...)
//and continues below everytime I add more modifier types..
}
Log(Value); // Strength = 33;
Edit: I'll just post (Credit: https://forum.unity.com/threads/tutorial-character-stats-aka-attributes-system.504095/) the imperative code in case someone needs it, I also have a hard time reading this one:
private float CalculateFinalValue()
{
float finalValue = BaseValue;
float sumPercentAdd = 0; // This will hold the sum of our "PercentAdd" modifiers
for (int i = 0; i < statModifiers.Count; i++)
{
StatModifier mod = statModifiers[i];
if (mod.Type == StatModType.Flat)
{
finalValue += mod.Value;
}
else if (mod.Type == StatModType.PercentAdd) // When we encounter a "PercentAdd" modifier
{
sumPercentAdd += mod.Value; // Start adding together all modifiers of this type
// If we're at the end of the list OR the next modifer isn't of this type
if (i + 1 >= statModifiers.Count || statModifiers[i + 1].Type != StatModType.PercentAdd)
{
finalValue *= 1 + sumPercentAdd; // Multiply the sum with the "finalValue", like we do for "PercentMult" modifiers
sumPercentAdd = 0; // Reset the sum back to 0
}
}
else if (mod.Type == StatModType.PercentMult) // Percent renamed to PercentMult
{
finalValue *= 1 + mod.Value;
}
}
return (float)Math.Round(finalValue, 4);
}
How can I add conditional inside Aggregate / Reduce / Scan function?
I suggest extracting model in both cases i.e.
Simple Example:
private static Dictionary<Color, float> s_ColorScores = new Dictionary<Color, float>() {
{Color.Red, 10.0f},
{Color.Yellow, 5.0f},
{Color.Green, 2.0f},
};
...
float Score = Colors
.Sum(color => s_ColorScores[color]);
Complex Example:
private static Dictionary<StatModType, Func<float, float, float>> s_Modifications = new
Dictionary<StatModType, Func<float, float, float>> {
{StatModType.Flat, (prior, value) => prior + value},
{StatModType.PercentAdd, (prior, value) => prior + prior * value},
//TODO: add modification rules here
};
public float Value => StatModifiers
.Aggregate(_baseValue, (prior, mod) => s_Modifications[mod.Type](prior, mod.Value));
So you are going to have game's model (s_ColorScores, s_Modifications...) with rules, settings, balances etc. (which you will probably want to tune, may be Color.Yellow score of 6.0f is a better choice) separated from simple business logics.
Assuming that the behaviors associated to the enum types are static and not dynamic, based on this MSDocs article another approach would be to use enumeration classes instead of enum types. To simplify this, you could use the SmartEnum package.
Using this lib and approach, your use cases turn into:
Simple Example:
public sealed class Color: SmartEnum<Color>
{
public static readonly Color Red = new Color (nameof(Red), 1, 10.0f);
public static readonly Color Yellow = new Color (nameof(Yellow), 2, 20.0f);
public static readonly Color Green = new Color (nameof(Green), 3, 30.0f);
private Color(string name, int value, double score)
: base(name, value)
{
this.Score = score;
}
public float Score {get;}
}
float TotalScore = Colors
.Sum(color => color.Score);
Complex Example:
public sealed class StatMod: SmartEnum<StatMod>
{
public static readonly StatMod FlatAdd = new StatMod(nameof(FlatAdd), 200, (prev, val)=>prev+val);
public static readonly StatMod PercentAdd = new StatMod(nameof(PercentAdd), 300, (prev,val)=>prior + prior * value);
private StatMod(string name, int value, Func<float, float, float> reduce) : base(name, value)
{
this.Reduce = reduce;
}
public Func<float, float, float> Reduce {get;}
}
public float Value => StatModifiers
.Aggregate(_baseValue, (prior, mod) => mod.Reduce(prev, mod.Value));
This is the implementation from Microsoft for Sinh of a Complex
public static Complex Sinh(Complex value) /* Hyperbolic sin */
{
double a = value.m_real;
double b = value.m_imaginary;
return new Complex(Math.Sinh(a) * Math.Cos(b), Math.Cosh(a) * Math.Sin(b));
}
and the implementation for Cosh
public static Complex Cos(Complex value) {
double a = value.m_real;
double b = value.m_imaginary;
return new Complex(Math.Cos(a) * Math.Cosh(b), - (Math.Sin(a) * Math.Sinh(b)));
}
and finally the the implementation for Tanh
public static Complex Tanh(Complex value) /* Hyperbolic tan */
{
return (Sinh(value) / Cosh(value));
}
Source: https://referencesource.microsoft.com/System.Numerics/a.html#e62f37ac1d0c67da
I don't understand why Microsoft implented the Tanh method that way?
It will fail for very large values. E.g.:
tanh(709 + 0i) --> 1, ok
tanh(711 + 0i) --> NaN, failed should be 1
Any ideas how to improve the tanh method that?
For double the Math.Tanh methods works for large values.
The complex tanh method could be implemented like that:
public static Complex Tanh(Complex value)
{
double a = value.Real;
double b = value.Imaginary;
double tanh_a = Math.Tanh(a);
double tan_b = Math.Tan(b);
Complex num = new Complex(tanh_a, tan_b);
Complex den = new Complex(1, tanh_a * tan_b);
return num / den;
}
This will work as well for large values, see https://dotnetfiddle.net/xGWdQt.
Update
As well the complex tan method needs to be re-implemented that it works with larges values (imaginary part):
public static Complex Tan(Complex value)
{
double a = value.Real;
double b = value.Imaginary;
double tan_a = Math.Tan(a);
double tanh_b = Math.Tanh(b);
Complex num = new Complex(tan_a, tanh_b);
Complex den = new Complex(1, -tan_a * tanh_b);
return num / den;
}
See https://dotnetfiddle.net/dh6CSG.
Using the comment from Hans Passant another way to implement the tanh method would be:
public static Complex Tanh(Complex value)
{
if (Math.Abs(value.Real) > 20)
return new Complex(Math.Sign(value.Real), 0);
else
return Complex.Tanh(value);
}
See https://dotnetfiddle.net/QvUECX.
And the tan method:
public static Complex Tan(Complex value)
{
if (Math.Abs(value.Imaginary) > 20)
return new Complex(0, Math.Sign(value.Imaginary));
else
return Complex.Tan(value);
}
See https://dotnetfiddle.net/Xzclcu.
I'm at a loss as to why I can't get this seemingly simple problem solved using Microsoft Solver Foundation.
All I need is to modify the weights (numbers) of certain observations to ensure that no 1 observation's weight AS A PERCENTAGE exceeds 25%. This is for the purposes of later calculating a constrained weighted average with the results of this algorithm.
For example, given the 5 weights of { 45, 100, 33, 500, 28 }, I would expect the result of this algorithm to be { 45, 53, 33, 53, 28 }, where 2 of the numbers had to be reduced such that they're within the 25% threshold of the new total (212 = 45+53+33+53+28) while the others remained untouched. Note that even though initially, the 2nd weight of 100 was only 14% of the total (706), as a result of decreasing the 4th weight of 500, it subsequently pushed up the % of the other observations and therein lies the only challenge with this.
I tried to recreate this using Solver only for it to tell me that it is the solution is "Infeasible" and it just returns all 1s. Update: solution need not use Solver, any alternative is welcome so long as it is fast when dealing with a decent number of weights.
var solver = SolverContext.GetContext();
var model = solver.CreateModel();
var decisionList = new List<Decision>();
decisionList.Add(new Decision(Domain.IntegerRange(1, 45), "Dec1"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 100), "Dec2"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 33), "Dec3"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 500), "Dec4"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 28), "Dec5"));
model.AddDecisions(decisionList.ToArray());
int weightLimit = 25;
foreach (var decision in model.Decisions)
{
model.AddConstraint(decision.Name + "weightLimit", 100 * (decision / Model.Sum(model.Decisions.ToArray())) <= weightLimit);
}
model.AddGoal("calcGoal", GoalKind.Maximize, Model.Sum(model.Decisions.ToArray()));
var solution = solver.Solve();
foreach (var decision in model.Decisions)
{
Debug.Print(decision.GetDouble().ToString());
}
Debug.Print("Solution Quality: " + solution.Quality.ToString());
Any help with this would be very much appreciated, thanks in advance.
I ditched Solver b/c it didn't live up to its name imo (or I didn't live up to its standards :)). Below is where I landed. Because this function gets used many times and on large lists of input weights, efficiency and performance are key so this function attempts to do the least # of iterations possible (let me know if anyone has any suggested improvements though). The results get used for a weighted average so I use "AttributeWeightPair" to store the value (attribute) and its weight and the function below is what modifies the weights to be within the constraint when given a list of these AWPs. The function assumes that weightLimit is passed in as a %, e.g. 25% gets passed in as 25, not 0.25 --- ok I'll stop stating what'll be obvious from the code - so here it is:
public static List<AttributeWeightPair<decimal>> WeightLimiter(List<AttributeWeightPair<decimal>> source, decimal weightLimit)
{
weightLimit /= 100; //convert to percentage
var zeroWeights = source.Where(w => w.Weight == 0).ToList();
var nonZeroWeights = source.Where(w => w.Weight > 0).ToList();
if (nonZeroWeights.Count == 0)
return source;
//return equal weights if given infeasible constraint
if ((1m / nonZeroWeights.Count()) > weightLimit)
{
nonZeroWeights.ForEach(w => w.Weight = 1);
return nonZeroWeights.Concat(zeroWeights).ToList();
}
//return original list if weight-limiting is unnecessary
if ((nonZeroWeights.Max(w => w.Weight) / nonZeroWeights.Sum(w => w.Weight)) <= weightLimit)
{
return source;
}
//sort (ascending) and store original weights
nonZeroWeights = nonZeroWeights.OrderBy(w => w.Weight).ToList();
var originalWeights = nonZeroWeights.Select(w => w.Weight).ToList();
//set starting point and determine direction from there
var initialSumWeights = nonZeroWeights.Sum(w => w.Weight);
var initialLimit = weightLimit * initialSumWeights;
var initialSuspects = nonZeroWeights.Where(w => w.Weight > initialLimit).ToList();
var initialTarget = weightLimit * (initialSumWeights - (initialSuspects.Sum(w => w.Weight) - initialLimit * initialSuspects.Count()));
var antepenultimateIndex = Math.Max(nonZeroWeights.FindLastIndex(w => w.Weight <= initialTarget), 1); //needs to be at least 1
for (int i = antepenultimateIndex; i < nonZeroWeights.Count(); i++)
{
nonZeroWeights[i].Weight = originalWeights[antepenultimateIndex - 1]; //set cap equal to the preceding weight
}
bool goingUp = (nonZeroWeights[antepenultimateIndex].Weight / nonZeroWeights.Sum(w => w.Weight)) > weightLimit ? false : true;
//Procedure 1 - find the weight # at which a cap would result in a weight % just UNDER the weight limit
int penultimateIndex = antepenultimateIndex;
bool justUnderTarget = false;
while (!justUnderTarget)
{
for (int i = penultimateIndex; i < nonZeroWeights.Count(); i++)
{
nonZeroWeights[i].Weight = originalWeights[penultimateIndex - 1]; //set cap equal to the preceding weight
}
var currentMaxPcntWeight = nonZeroWeights[penultimateIndex].Weight / nonZeroWeights.Sum(w => w.Weight);
if (currentMaxPcntWeight == weightLimit)
{
return nonZeroWeights.Concat(zeroWeights).ToList();
}
else if (goingUp && currentMaxPcntWeight < weightLimit)
{
nonZeroWeights[penultimateIndex].Weight = originalWeights[penultimateIndex]; //reset
if (penultimateIndex < nonZeroWeights.Count() - 1)
penultimateIndex++; //move up
else break;
}
else if (!goingUp && currentMaxPcntWeight > weightLimit)
{
if (penultimateIndex > 1)
penultimateIndex--; //move down
else break;
}
else
{
justUnderTarget = true;
}
}
if (goingUp) //then need to back up a step
{
penultimateIndex = (penultimateIndex > 1 ? penultimateIndex - 1 : 1);
for (int i = penultimateIndex; i < nonZeroWeights.Count(); i++)
{
nonZeroWeights[i].Weight = originalWeights[penultimateIndex - 1];
}
}
//Procedure 2 - increment the modified weights (subject to a cap equal to their original values) until the weight limit is hit (allowing a very slight overage for the last term in some cases)
int ultimateIndex = penultimateIndex;
var sumWeights = nonZeroWeights.Sum(w => w.Weight); //use this counter instead of summing every time for condition check within loop
bool justOverTarget = false;
while (!justOverTarget)
{
for (int i = ultimateIndex; i < nonZeroWeights.Count(); i++)
{
if (nonZeroWeights[i].Weight + 1 > originalWeights[i])
{
if (ultimateIndex < nonZeroWeights.Count() - 1)
ultimateIndex++;
else justOverTarget = true;
}
else
{
nonZeroWeights[i].Weight++;
sumWeights++;
}
}
if ((nonZeroWeights.Last().Weight / sumWeights) >= weightLimit)
{
justOverTarget = true;
}
}
return nonZeroWeights.Concat(zeroWeights).ToList();
}
public class AttributeWeightPair<T>
{
public T Attribute { get; set; }
public decimal? Weight { get; set; }
public AttributeWeightPair(T attribute, decimal? count)
{
this.Attribute = attribute;
this.Weight = count;
}
}