|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.ML.SearchSpace.Converter;
namespace Microsoft.ML.SearchSpace
{
/// <summary>
/// Parameter type. This type is used to determine the type of <see cref="Parameter"/> and is associated to corresponded Json token when serializing/deserializing.
/// </summary>
public enum ParameterType
{
/// <summary>
/// Json int type.
/// </summary>
Integer = 0,
/// <summary>
/// Json number type.
/// </summary>
Number = 1,
/// <summary>
/// Json boolean type.
/// </summary>
Bool = 2,
/// <summary>
/// Json string type.
/// </summary>
String = 3,
/// <summary>
/// Json object type.
/// </summary>
Object = 4,
/// <summary>
/// Json array type.
/// </summary>
Array = 5,
}
/// <summary>
/// <see cref="Parameter"/> is used to save sweeping result from tuner and is used to restore mlnet pipeline from sweepable pipeline.
/// </summary>
/// <example>
/// <format type="text/markdown">
/// <![CDATA[
/// [!code-csharp[AutoMLExperiment](~/../docs/samples/docs/samples/Microsoft.ML.AutoML.Samples/Sweepable/ParameterExample.cs)]
/// ]]>
/// </format>
/// </example>
[JsonConverter(typeof(ParameterConverter))]
public sealed class Parameter : IDictionary<string, Parameter>, IEquatable<Parameter>, IEqualityComparer<Parameter>
{
private readonly JsonSerializerOptions _settings = new JsonSerializerOptions()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
private readonly object _value;
private Parameter(object value, ParameterType type)
{
_value = value;
ParameterType = type;
_settings.Converters.Add(new JsonStringEnumConverter());
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="double"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Number"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromDouble(double value)
{
return new Parameter(value, ParameterType.Number);
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="float"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Number"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromFloat(float value)
{
return new Parameter(value, ParameterType.Number);
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="long"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Integer"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromLong(long value)
{
return new Parameter(value, ParameterType.Integer);
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="int"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Integer"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromInt(int value)
{
return new Parameter(value, ParameterType.Integer);
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="string"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.String"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromString(string value)
{
return new Parameter(value, ParameterType.String);
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="bool"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Bool"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromBool(bool value)
{
return new Parameter(value, ParameterType.Bool);
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="Enum"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.String"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromEnum<T>(T value) where T : struct, Enum
{
return Parameter.FromEnum(value, typeof(T));
}
/// <summary>
/// Create a <see cref="Parameter"/> from a <see cref="IEnumerable"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Array"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromIEnumerable<T>(IEnumerable<T> values)
{
// check T
return Parameter.FromIEnumerable(values as IEnumerable);
}
private static Parameter FromIEnumerable(IEnumerable values)
{
return new Parameter(values, ParameterType.Array);
}
private static Parameter FromEnum(Enum e, Type t)
{
return Parameter.FromString(Enum.GetName(t, e));
}
/// <summary>
/// Create a <see cref="Parameter"/> from an <see cref="object"/> value. The <see cref="ParameterType"/> will be <see cref="ParameterType.Object"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter FromObject<T>(T value) where T : class
{
return Parameter.FromObject(value, typeof(T));
}
private static Parameter FromObject(object value, Type type)
{
var param = value switch
{
int i => Parameter.FromInt(i),
long l => Parameter.FromLong(l),
double d => Parameter.FromDouble(d),
float f => Parameter.FromFloat(f),
string s => Parameter.FromString(s),
bool b => Parameter.FromBool(b),
IEnumerable vs => Parameter.FromIEnumerable(vs),
Enum e => Parameter.FromEnum(e, e.GetType()),
_ => null,
};
if (param != null)
{
return param;
}
else
{
var parameter = Parameter.CreateNestedParameter();
var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p => p.CanRead && p.CanWrite);
foreach (var property in properties)
{
var name = property.Name;
var pValue = property.GetValue(value);
if (pValue != null)
{
var p = Parameter.FromObject(pValue, property.PropertyType);
if (p?.Count != 0)
{
parameter[name] = p;
}
}
}
return parameter;
}
}
/// <summary>
/// Create a <see cref="Parameter"/> from <paramref name="parameters"/>. The <see cref="ParameterType"/> will be <see cref="ParameterType.Object"/>.
/// </summary>
/// <returns><see cref="Parameter"/></returns>
public static Parameter CreateNestedParameter(params KeyValuePair<string, Parameter>[] parameters)
{
var parameter = new Parameter(new Dictionary<string, Parameter>(), ParameterType.Object);
foreach (var param in parameters)
{
parameter[param.Key] = param.Value;
}
return parameter;
}
internal object Value { get => _value; }
/// <inheritdoc/>
public int Count => ParameterType == ParameterType.Object ? (_value as Dictionary<string, Parameter>).Count : 1;
/// <inheritdoc/>
public bool IsReadOnly
{
get
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>)?.IsReadOnly ?? false;
}
}
/// <summary>
/// Get <see cref="ParameterType"/> of this <see cref="ParameterType"/>
/// </summary>
public ParameterType ParameterType { get; }
ICollection<Parameter> IDictionary<string, Parameter>.Values
{
get
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).Values;
}
}
/// <inheritdoc/>
public ICollection<string> Keys
{
get
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).Keys;
}
}
/// <inheritdoc/>
public Parameter this[string key]
{
get
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>)[key];
}
set
{
VerifyIfParameterIsObjectType();
(_value as IDictionary<string, Parameter>)[key] = value;
}
}
/// <summary>
/// Cast <see cref="ParameterType"/> to <typeparamref name="T"/>. This method will return immediately if the underlying value is of type <typeparamref name="T"/>, otherwise it uses <see cref="JsonSerializer"/> to
/// convert its value to <typeparamref name="T"/>.
/// </summary>
public T AsType<T>()
{
if (_value is T t)
{
return t;
}
else
{
var json = JsonSerializer.Serialize(_value, _settings);
return JsonSerializer.Deserialize<T>(json, _settings);
}
}
/// <inheritdoc/>
public void Clear()
{
VerifyIfParameterIsObjectType();
(_value as Dictionary<string, Parameter>).Clear();
}
/// <inheritdoc/>
public void Add(string key, Parameter value)
{
VerifyIfParameterIsObjectType();
(_value as Dictionary<string, Parameter>).Add(key, value);
}
/// <inheritdoc/>
public bool TryGetValue(string key, out Parameter value)
{
VerifyIfParameterIsObjectType();
return (_value as Dictionary<string, Parameter>).TryGetValue(key, out value);
}
/// <inheritdoc/>
public void Add(KeyValuePair<string, Parameter> item)
{
VerifyIfParameterIsObjectType();
(_value as Dictionary<string, Parameter>).Add(item.Key, item.Value);
}
/// <inheritdoc/>
public bool Contains(KeyValuePair<string, Parameter> item)
{
VerifyIfParameterIsObjectType();
return (_value as Dictionary<string, Parameter>).Contains(item);
}
/// <inheritdoc/>
public bool Remove(KeyValuePair<string, Parameter> item)
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).Remove(item);
}
/// <inheritdoc/>
IEnumerator<KeyValuePair<string, Parameter>> IEnumerable<KeyValuePair<string, Parameter>>.GetEnumerator()
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).GetEnumerator();
}
private void VerifyIfParameterIsObjectType()
{
Contract.Assert(ParameterType == ParameterType.Object, "parameter is not object type.");
}
/// <inheritdoc/>
public void CopyTo(KeyValuePair<string, Parameter>[] array, int arrayIndex)
{
VerifyIfParameterIsObjectType();
(_value as IDictionary<string, Parameter>).CopyTo(array, arrayIndex);
}
/// <inheritdoc/>
public bool ContainsKey(string key)
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).ContainsKey(key);
}
/// <inheritdoc/>
public bool Remove(string key)
{
VerifyIfParameterIsObjectType();
return (_value as IDictionary<string, Parameter>).Remove(key);
}
/// <inheritdoc/>
public bool Equals(Parameter other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
var thisJson = JsonSerializer.Serialize(this);
var otherJson = JsonSerializer.Serialize(other);
return thisJson == otherJson;
}
/// <inheritdoc/>
public override int GetHashCode()
{
var thisJson = JsonSerializer.Serialize(this);
return thisJson.GetHashCode();
}
/// <summary>
/// Determines wether two <see cref="Parameter"/> objects have the same value.
/// </summary>
/// <param name="x">The first parameter to compare.</param>
/// <param name="y">The second parameter to compare.</param>
/// <returns>true if the value of <paramref name="x" /> is the same as the value of <paramref name="y" />; otherwise, false.</returns>
public bool Equals(Parameter x, Parameter y)
{
return x.GetHashCode() == y.GetHashCode();
}
/// <inheritdoc/>
public int GetHashCode(Parameter obj)
{
return obj.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
}
|