Skip to content

Commit 2ca6a87

Browse files
author
Chris Elion
authored
[MLA-1879] culture-invariant sorting for sensors and actuators (#5194)
1 parent c0ad675 commit 2ca6a87

File tree

8 files changed

+113
-10
lines changed

8 files changed

+113
-10
lines changed

com.unity.ml-agents/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ depend on the previous behavior, you can explicitly set the Agent's `InferenceDe
4444

4545
### Bug Fixes
4646
#### com.unity.ml-agents / com.unity.ml-agents.extensions (C#)
47+
- Fixed a bug where sensors and actuators could get sorted inconsistently on different systems to different Culture
48+
settings. Unfortunately, this may require retraining models if it changes the resulting order of the sensors
49+
or actuators on your system. (#5194)
4750
#### ml-agents / ml-agents-envs / gym-unity (Python)
4851

4952

com.unity.ml-agents/Runtime/Actuators/ActuatorManager.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Unity.MLAgents.Actuators
1212
internal class ActuatorManager : IList<IActuator>
1313
{
1414
// IActuators managed by this object.
15-
IList<IActuator> m_Actuators;
15+
List<IActuator> m_Actuators;
1616

1717
// An implementation of IDiscreteActionMask that allows for writing to it based on an offset.
1818
ActuatorDiscreteActionMask m_DiscreteActionMask;
@@ -95,7 +95,7 @@ internal void ReadyActuatorsForExecution(IList<IActuator> actuators, int numCont
9595
#endif
9696

9797
// Sort the Actuators by name to ensure determinism
98-
SortActuators();
98+
SortActuators(m_Actuators);
9999
var continuousActions = numContinuousActions == 0 ? ActionSegment<float>.Empty :
100100
new ActionSegment<float>(new float[numContinuousActions]);
101101
var discreteActions = numDiscreteBranches == 0 ? ActionSegment<int>.Empty : new ActionSegment<int>(new int[numDiscreteBranches]);
@@ -317,11 +317,9 @@ public void ResetData()
317317
/// <summary>
318318
/// Sorts the <see cref="IActuator"/>s according to their <see cref="IActuator.Name"/> value.
319319
/// </summary>
320-
void SortActuators()
320+
internal static void SortActuators(List<IActuator> actuators)
321321
{
322-
((List<IActuator>)m_Actuators).Sort((x,
323-
y) => x.Name
324-
.CompareTo(y.Name));
322+
actuators.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.InvariantCulture));
325323
}
326324

327325
/// <summary>

com.unity.ml-agents/Runtime/Agent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,7 @@ internal void InitializeSensors()
989989
}
990990

991991
// Sort the Sensors by name to ensure determinism
992-
sensors.Sort((x, y) => x.GetName().CompareTo(y.GetName()));
992+
SensorUtils.SortSensors(sensors);
993993

994994
#if DEBUG
995995
// Make sure the names are actually unique

com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using Unity.Barracuda;
@@ -34,7 +35,7 @@ public static string[] GetInputNames(this Model model)
3435
names.Add(mem.input);
3536
}
3637

37-
names.Sort();
38+
names.Sort(StringComparer.InvariantCulture);
3839

3940
return names.ToArray();
4041
}
@@ -87,7 +88,7 @@ public static IReadOnlyList<TensorProxy> GetInputTensors(this Model model)
8788
});
8889
}
8990

90-
tensors.Sort((el1, el2) => el1.name.CompareTo(el2.name));
91+
tensors.Sort((el1, el2) => string.Compare(el1.name, el2.name, StringComparison.InvariantCulture));
9192

9293
return tensors;
9394
}
@@ -150,7 +151,7 @@ public static string[] GetOutputNames(this Model model)
150151
}
151152
}
152153

153-
names.Sort();
154+
names.Sort(StringComparer.InvariantCulture);
154155

155156
return names.ToArray();
156157
}

com.unity.ml-agents/Runtime/Sensors/ISensor.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
15
namespace Unity.MLAgents.Sensors
26
{
37
/// <summary>
@@ -124,4 +128,13 @@ public static int ObservationSize(this ISensor sensor)
124128
return count;
125129
}
126130
}
131+
132+
internal static class SensorUtils
133+
{
134+
internal static void SortSensors(List<ISensor> sensors)
135+
{
136+
// Use InvariantCulture to ensure consistent sorting between different culture settings.
137+
sensors.Sort((x, y) => string.Compare(x.GetName(), y.GetName(), StringComparison.InvariantCulture));
138+
}
139+
}
127140
}

com.unity.ml-agents/Tests/Editor/Actuators/ActuatorManagerTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
24
using System.Linq;
35
using NUnit.Framework;
46
using Unity.MLAgents.Actuators;
@@ -321,5 +323,31 @@ public void TestHeuristic()
321323
Assert.IsTrue(va2.m_HeuristicCalled);
322324
Assert.AreEqual(va2.m_DiscreteBufferSize, 4);
323325
}
326+
327+
328+
/// <summary>
329+
/// Test that sensors sort by name consistently across culture settings.
330+
/// Example strings and cultures taken from
331+
/// https://docs.microsoft.com/en-us/globalization/locale/sorting-and-string-comparison
332+
/// </summary>
333+
/// <param name="culture"></param>
334+
[TestCase("da-DK")]
335+
[TestCase("en-US")]
336+
public void TestSortActuators(string culture)
337+
{
338+
List<IActuator> actuators = new List<IActuator>();
339+
var actuator0 = new TestActuator(ActionSpec.MakeContinuous(2), "Apple");
340+
var actuator1 = new TestActuator(ActionSpec.MakeContinuous(2), "Æble");
341+
actuators.Add(actuator0);
342+
actuators.Add(actuator1);
343+
344+
var originalCulture = CultureInfo.CurrentCulture;
345+
CultureInfo.CurrentCulture = new CultureInfo(culture);
346+
ActuatorManager.SortActuators(actuators);
347+
CultureInfo.CurrentCulture = originalCulture;
348+
349+
Assert.AreEqual(actuator1, actuators[0]);
350+
Assert.AreEqual(actuator0, actuators[1]);
351+
}
324352
}
325353
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using NUnit.Framework;
5+
using UnityEngine;
6+
using Unity.MLAgents.Sensors;
7+
using Unity.MLAgents.Utils.Tests;
8+
9+
namespace Unity.MLAgents.Tests
10+
{
11+
12+
[TestFixture]
13+
public class SensorUtilTests
14+
{
15+
internal class TempCulture : IDisposable
16+
{
17+
private CultureInfo m_OriginalCulture;
18+
19+
internal TempCulture(CultureInfo newCulture)
20+
{
21+
m_OriginalCulture = CultureInfo.CurrentCulture;
22+
CultureInfo.CurrentCulture = newCulture;
23+
}
24+
25+
public void Dispose()
26+
{
27+
CultureInfo.CurrentCulture = m_OriginalCulture;
28+
}
29+
}
30+
31+
/// <summary>
32+
/// Test that sensors sort by name consistently across culture settings.
33+
/// Example strings and cultures taken from
34+
/// https://docs.microsoft.com/en-us/globalization/locale/sorting-and-string-comparison
35+
/// </summary>
36+
/// <param name="culture"></param>
37+
[TestCase("da-DK")]
38+
[TestCase("en-US")]
39+
public void TestSortCulture(string culture)
40+
{
41+
List<ISensor> sensors = new List<ISensor>();
42+
var sensor0 = new TestSensor("Apple");
43+
var sensor1 = new TestSensor("Æble");
44+
sensors.Add(sensor0);
45+
sensors.Add(sensor1);
46+
47+
var originalCulture = CultureInfo.CurrentCulture;
48+
CultureInfo.CurrentCulture = new CultureInfo(culture);
49+
SensorUtils.SortSensors(sensors);
50+
CultureInfo.CurrentCulture = originalCulture;
51+
52+
Assert.AreEqual(sensor1, sensors[0]);
53+
Assert.AreEqual(sensor0, sensors[1]);
54+
}
55+
56+
}
57+
}

com.unity.ml-agents/Tests/Runtime/Sensor/SensorUtilTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)