Embed primitive timeline facts in IPA trials
This commit is contained in:
parent
7fa318736c
commit
f16ea641af
11 changed files with 392 additions and 21 deletions
|
|
@ -28,7 +28,19 @@ public sealed record IpaTrialResult(
|
|||
[property: Key(16)] string EvaluationSummary,
|
||||
[property: Key(17)] string Verdict,
|
||||
[property: Key(18)] string[] KnownLies,
|
||||
[property: Key(19)] SpeechTimingReceipt[] TimingReceipts);
|
||||
[property: Key(19)] SpeechTimingReceipt[] TimingReceipts,
|
||||
[property: Key(20)] PrimitiveTimelineFact[] TimelineFacts);
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed record PrimitiveTimelineFact(
|
||||
[property: Key(0)] string Name,
|
||||
[property: Key(1)] string Primitive,
|
||||
[property: Key(2)] string Signal,
|
||||
[property: Key(3)] float Value,
|
||||
[property: Key(4)] string Unit,
|
||||
[property: Key(5)] int BlockStart,
|
||||
[property: Key(6)] int BlockEnd,
|
||||
[property: Key(7)] string Summary);
|
||||
|
||||
public static class IpaTrialResultCultCacheStore
|
||||
{
|
||||
|
|
|
|||
157
src/AquaSynth.Core/PrimitiveTimelineFactExtractor.cs
Normal file
157
src/AquaSynth.Core/PrimitiveTimelineFactExtractor.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace AquaSynth.Dsl;
|
||||
|
||||
public static class PrimitiveTimelineFactExtractor
|
||||
{
|
||||
public static IReadOnlyList<PrimitiveTimelineFact> Extract(IReadOnlyList<ProbeTimelineSample> timeline)
|
||||
{
|
||||
if (timeline.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var facts = new List<PrimitiveTimelineFact>
|
||||
{
|
||||
Fact(
|
||||
"timeline_blocks",
|
||||
"timeline",
|
||||
"block_count",
|
||||
timeline.Select(sample => sample.Block).Distinct().Count(),
|
||||
"blocks",
|
||||
timeline.Min(sample => sample.Block),
|
||||
timeline.Max(sample => sample.Block),
|
||||
"Observed primitive timeline block count.")
|
||||
};
|
||||
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "contact:", signal: "opening", "contact_min_opening", "normalized", Min);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "contact:", signal: "opening", "contact_mean_opening", "normalized", Mean);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "contact:", signal: "opening", "contact_closed_blocks", "blocks", ClosedBlocks);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "contact:", signal: "reservoir", "contact_reservoir_peak", "pressure", MaxAbs);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "contact:", signal: "released_flow", "contact_release_peak", "flow", MaxAbs);
|
||||
AddPeakBlockFacts(facts, timeline, "contact:", "released_flow", "contact_release_peak_block");
|
||||
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "source:", signal: "load_pressure", "source_load_pressure_peak", "pressure", MaxAbs);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "source:", signal: "flow", "source_flow_peak", "flow", MaxAbs);
|
||||
AddPeakBlockFacts(facts, timeline, "source:", "flow", "source_flow_peak_block");
|
||||
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "branch:", signal: "admittance", "branch_admittance_peak", "admittance", MaxAbs);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "branch:", signal: "exchanged_flow", "branch_exchanged_flow_peak", "flow", MaxAbs);
|
||||
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "radiation:", signal: "boundary_flow", "radiation_boundary_flow_peak", "flow", MaxAbs);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "radiation:", signal: "output", "radiation_output_peak", "amplitude", MaxAbs);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "radiation:", signal: "output", "radiation_output_mean_abs", "amplitude", MeanAbs);
|
||||
AddPeakBlockFacts(facts, timeline, "radiation:", "output", "radiation_output_peak_block");
|
||||
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "path:", signal: "area", "path_mean_area", "area", Mean);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "path:", signal: "delay_samples", "path_mean_delay_samples", "samples", Mean);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "path:", signal: "energy_in", "path_energy_in_sum", "energy", Sum);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "path:", signal: "energy_out", "path_energy_out_sum", "energy", Sum);
|
||||
AddSignalFacts(facts, timeline, primitivePrefix: "path:", signal: "passivity_ratio", "path_passivity_max", "ratio", Max);
|
||||
|
||||
return facts
|
||||
.Where(fact => float.IsFinite(fact.Value))
|
||||
.OrderBy(fact => fact.Name, StringComparer.Ordinal)
|
||||
.ThenBy(fact => fact.Primitive, StringComparer.Ordinal)
|
||||
.ThenBy(fact => fact.BlockStart)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static void AddSignalFacts(
|
||||
List<PrimitiveTimelineFact> facts,
|
||||
IReadOnlyList<ProbeTimelineSample> timeline,
|
||||
string primitivePrefix,
|
||||
string signal,
|
||||
string name,
|
||||
string unit,
|
||||
Func<IReadOnlyList<ProbeTimelineSample>, float> reducer)
|
||||
{
|
||||
foreach (var group in Matching(timeline, primitivePrefix, signal).GroupBy(sample => sample.Primitive, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var samples = group.ToArray();
|
||||
if (samples.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = reducer(samples);
|
||||
facts.Add(Fact(
|
||||
name,
|
||||
group.Key,
|
||||
signal,
|
||||
value,
|
||||
unit,
|
||||
samples.Min(sample => sample.Block),
|
||||
samples.Max(sample => sample.Block),
|
||||
Summary(name, group.Key, signal, value, unit)));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddPeakBlockFacts(
|
||||
List<PrimitiveTimelineFact> facts,
|
||||
IReadOnlyList<ProbeTimelineSample> timeline,
|
||||
string primitivePrefix,
|
||||
string signal,
|
||||
string name)
|
||||
{
|
||||
foreach (var group in Matching(timeline, primitivePrefix, signal).GroupBy(sample => sample.Primitive, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var peak = group
|
||||
.OrderByDescending(sample => Math.Abs(sample.Value))
|
||||
.ThenBy(sample => sample.Block)
|
||||
.FirstOrDefault();
|
||||
if (peak is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
facts.Add(Fact(
|
||||
name,
|
||||
peak.Primitive,
|
||||
signal,
|
||||
peak.Block,
|
||||
"block",
|
||||
peak.Block,
|
||||
peak.Block,
|
||||
Summary(name, peak.Primitive, signal, peak.Block, "block")));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ProbeTimelineSample> Matching(
|
||||
IReadOnlyList<ProbeTimelineSample> timeline,
|
||||
string primitivePrefix,
|
||||
string signal) =>
|
||||
timeline.Where(sample =>
|
||||
sample.Primitive.StartsWith(primitivePrefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
sample.Signal.Equals(signal, StringComparison.OrdinalIgnoreCase) &&
|
||||
float.IsFinite(sample.Value));
|
||||
|
||||
private static PrimitiveTimelineFact Fact(
|
||||
string name,
|
||||
string primitive,
|
||||
string signal,
|
||||
float value,
|
||||
string unit,
|
||||
int blockStart,
|
||||
int blockEnd,
|
||||
string summary) =>
|
||||
new(name, primitive, signal, value, unit, blockStart, blockEnd, summary);
|
||||
|
||||
private static string Summary(string name, string primitive, string signal, float value, string unit) =>
|
||||
$"{name} {primitive} {signal} {value.ToString("0.######", CultureInfo.InvariantCulture)} {unit}";
|
||||
|
||||
private static float Min(IReadOnlyList<ProbeTimelineSample> samples) => samples.Min(sample => sample.Value);
|
||||
|
||||
private static float Max(IReadOnlyList<ProbeTimelineSample> samples) => samples.Max(sample => sample.Value);
|
||||
|
||||
private static float Mean(IReadOnlyList<ProbeTimelineSample> samples) => samples.Average(sample => sample.Value);
|
||||
|
||||
private static float Sum(IReadOnlyList<ProbeTimelineSample> samples) => samples.Sum(sample => sample.Value);
|
||||
|
||||
private static float MaxAbs(IReadOnlyList<ProbeTimelineSample> samples) => samples.Max(sample => Math.Abs(sample.Value));
|
||||
|
||||
private static float MeanAbs(IReadOnlyList<ProbeTimelineSample> samples) => samples.Average(sample => Math.Abs(sample.Value));
|
||||
|
||||
private static float ClosedBlocks(IReadOnlyList<ProbeTimelineSample> samples) =>
|
||||
samples.Count(sample => sample.Value <= 0.2f);
|
||||
}
|
||||
|
|
@ -237,7 +237,8 @@ public static class IpaTrialOrchestrator
|
|||
0,
|
||||
Confidence(metrics),
|
||||
"Local worker generated the candidate patch, rendered Faust audio, compared log-mel/articulation evidence, and wrote CultCache trial result data.")
|
||||
]));
|
||||
],
|
||||
PrimitiveTimelineFactExtractor.Extract(ReadTimeline(candidate.TimelinePath)).ToArray()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,6 +329,7 @@ public static class IpaTrialOrchestrator
|
|||
{
|
||||
new("gesture_score", AgentGestureScore(mapped.target.Target, patch, timeline), .25f)
|
||||
};
|
||||
var timelineFacts = PrimitiveTimelineFactExtractor.Extract(timeline).ToArray();
|
||||
string verdict;
|
||||
string evaluation;
|
||||
var artifacts = new List<SpeechRenderArtifact>
|
||||
|
|
@ -394,7 +396,8 @@ public static class IpaTrialOrchestrator
|
|||
0,
|
||||
Confidence(metrics),
|
||||
"Local worker rendered an external Codex-authored patch, compared log-mel/articulation evidence, and wrote CultCache trial result data.")
|
||||
]));
|
||||
],
|
||||
timelineFacts));
|
||||
}
|
||||
|
||||
await IpaTrialResultCultCacheStore.UpsertResultsAsync(storePath, results).ConfigureAwait(false);
|
||||
|
|
@ -607,7 +610,7 @@ public static class IpaTrialOrchestrator
|
|||
private static string SummaryCsv(IReadOnlyList<IpaTrialResult> results)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("trial_id,target_set_id,candidate_id,reference_id,verdict,gesture_score,log_mel_cosine,log_mel_distance,audio_score,articulation_score,rms_ratio");
|
||||
builder.AppendLine("trial_id,target_set_id,candidate_id,reference_id,verdict,gesture_score,log_mel_cosine,log_mel_distance,audio_score,articulation_score,rms_ratio,contact_release_peak,radiation_output_peak,path_passivity_max");
|
||||
foreach (var result in results)
|
||||
{
|
||||
builder.Append(Escape(result.TrialId));
|
||||
|
|
@ -630,7 +633,13 @@ public static class IpaTrialOrchestrator
|
|||
builder.Append(',');
|
||||
builder.Append(Metric(result, "articulation_score"));
|
||||
builder.Append(',');
|
||||
builder.AppendLine(Metric(result, "rms_ratio"));
|
||||
builder.Append(Metric(result, "rms_ratio"));
|
||||
builder.Append(',');
|
||||
builder.Append(TimelineFact(result, "contact_release_peak"));
|
||||
builder.Append(',');
|
||||
builder.Append(TimelineFact(result, "radiation_output_peak"));
|
||||
builder.Append(',');
|
||||
builder.AppendLine(TimelineFact(result, "path_passivity_max"));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
|
|
@ -680,6 +689,38 @@ public static class IpaTrialOrchestrator
|
|||
(result.Metrics.FirstOrDefault(metric => metric.Name == name)?.Value ?? 0)
|
||||
.ToString("0.######", CultureInfo.InvariantCulture);
|
||||
|
||||
private static string TimelineFact(IpaTrialResult result, string name) =>
|
||||
(result.TimelineFacts?
|
||||
.Where(fact => fact.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(fact => fact.Value)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max() ?? 0)
|
||||
.ToString("0.######", CultureInfo.InvariantCulture);
|
||||
|
||||
private static IReadOnlyList<ProbeTimelineSample> ReadTimeline(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var samples = new List<ProbeTimelineSample>();
|
||||
foreach (var line in File.ReadLines(path).Skip(1))
|
||||
{
|
||||
var parts = line.Split(',');
|
||||
if (parts.Length < 4 ||
|
||||
!int.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var block) ||
|
||||
!float.TryParse(parts[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
samples.Add(new ProbeTimelineSample(block, parts[1], parts[2], value));
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
private static string SafeName(string value) =>
|
||||
new(value.Select(character => char.IsLetterOrDigit(character) ? character : '_').ToArray());
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue