Economy command: L-productive is one fault per unproductive component; a held-open catalog is a named refusal at the first read too
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ae3e4a7c58
commit
6befccce74
3 changed files with 63 additions and 14 deletions
|
|
@ -304,11 +304,11 @@ public static class EconomyChecks
|
|||
}
|
||||
|
||||
var final = Build(pick, 0);
|
||||
var (rho, component) = Radius(final, n);
|
||||
var faults = new List<EconomyFault>();
|
||||
if (rho >= 1 - Margin)
|
||||
string Name(int i) => i == labour ? "labour" : names[i];
|
||||
foreach (var (rho, component) in Radii(final, n))
|
||||
{
|
||||
string Name(int i) => i == labour ? "labour" : names[i];
|
||||
if (rho < 1 - Margin) continue;
|
||||
var order = CycleOrder(final, n, component);
|
||||
faults.Add(new EconomyFault(LProductive, Name(order[0]),
|
||||
$"The goods cannot sustain themselves: spectral radius {rho:0.######} is not below 1, carried by {string.Join(" -> ", order.Select(Name))}.",
|
||||
|
|
@ -336,9 +336,9 @@ public static class EconomyChecks
|
|||
return u;
|
||||
}
|
||||
|
||||
// The spectral radius of a nonnegative matrix: the largest over its strongly connected components that hold a cycle, each by
|
||||
// power iteration on A + I (primitive when irreducible). An edge j -> i is a nonzero A[i, j]. Also the component carrying it.
|
||||
private static (double Rho, List<int> Component) Radius(double[,] a, int n)
|
||||
// The spectral radius of every strongly connected component of a nonnegative matrix, each by power iteration on A + I
|
||||
// (primitive when irreducible). An edge j -> i is a nonzero A[i, j]. A component with no cycle has radius 0.
|
||||
private static List<(double Rho, List<int> Component)> Radii(double[,] a, int n)
|
||||
{
|
||||
var reach = new bool[n, n];
|
||||
for (var i = 0; i < n; i++)
|
||||
|
|
@ -350,8 +350,7 @@ public static class EconomyChecks
|
|||
for (var j = 0; j < n; j++)
|
||||
if (reach[k, j]) reach[i, j] = true;
|
||||
var done = new bool[n];
|
||||
var best = 0.0;
|
||||
var bestComponent = new List<int>();
|
||||
var radii = new List<(double, List<int>)>();
|
||||
for (var start = 0; start < n; start++)
|
||||
{
|
||||
if (done[start]) continue;
|
||||
|
|
@ -359,9 +358,9 @@ public static class EconomyChecks
|
|||
foreach (var i in component) done[i] = true;
|
||||
var rho = Irreducible(a, component);
|
||||
if (double.IsNaN(rho)) rho = double.PositiveInfinity;
|
||||
if (rho > best) { best = rho; bestComponent = component; }
|
||||
radii.Add((rho, component));
|
||||
}
|
||||
return (best, bestComponent);
|
||||
return radii;
|
||||
}
|
||||
|
||||
private static double Irreducible(double[,] a, List<int> nodes)
|
||||
|
|
|
|||
|
|
@ -416,6 +416,28 @@ public sealed class EconomyCommandTests
|
|||
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void ANewUnproductiveCycleBelowTheHeldRadiusIsRefusedAloneOrBesideALoweringOfTheHeldCycle(bool alsoLowerHeld)
|
||||
{
|
||||
using var f = EconomyFixture.Healthy();
|
||||
f.Good("loopa", inputs: new[] { ("loopb", 2) });
|
||||
f.Good("loopb", inputs: new[] { ("loopa", 4) });
|
||||
Flush(f);
|
||||
var bytes = File.ReadAllBytes(f.CatalogPath);
|
||||
var code = Author(f, true, out var output, a =>
|
||||
{
|
||||
if (alsoLowerHeld) Good(f, a, "loopb", inputs: new[] { ("loopa", 2) });
|
||||
Good(f, a, "loopc", inputs: new[] { ("loopd", 1) });
|
||||
Good(f, a, "loopd", inputs: new[] { ("loope", 1) });
|
||||
Good(f, a, "loope", inputs: new[] { ("loopc", 2) });
|
||||
});
|
||||
Assert.Equal(1, code);
|
||||
Assert.Contains("L-productive", output);
|
||||
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AHeldCycleWhoseRadiusFallsStillApplies()
|
||||
{
|
||||
|
|
@ -520,6 +542,23 @@ public sealed class EconomyCommandTests
|
|||
Assert.Empty(Directory.GetFiles(Path.GetDirectoryName(f.CatalogPath), "*.economy-*"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ACatalogHeldOpenForWritingIsANamedRefusalAndStaysIntact()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
using var f = EconomyFixture.Healthy();
|
||||
Flush(f);
|
||||
var bytes = File.ReadAllBytes(f.CatalogPath);
|
||||
using (new FileStream(f.CatalogPath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
|
||||
{
|
||||
var code = Author(f, true, out var output, a => Good(f, a, "coal"));
|
||||
Assert.Equal(1, code);
|
||||
Assert.Contains("close Unity", output);
|
||||
Assert.DoesNotContain(" at ", output);
|
||||
}
|
||||
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheSwapKeepsTheCatalogsFileMode()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -121,19 +121,26 @@ public static class EconomyCommand
|
|||
try
|
||||
{
|
||||
var trial = Path.Combine(scratch, "Aetheria.cc");
|
||||
var fingerprint = Fingerprint(catalogPath);
|
||||
File.Copy(catalogPath, trial);
|
||||
new FileInfo(trial).IsReadOnly = false;
|
||||
string fingerprint;
|
||||
Dictionary<string, EconomyFault> held;
|
||||
HashSet<string> recipesHeld;
|
||||
Dictionary<string, byte[]> original;
|
||||
using (var cache = AetheriaStores.Open(catalogPath, registry: registry))
|
||||
try
|
||||
{
|
||||
fingerprint = Fingerprint(catalogPath);
|
||||
File.Copy(catalogPath, trial);
|
||||
new FileInfo(trial).IsReadOnly = false;
|
||||
using var cache = AetheriaStores.Open(catalogPath, registry: registry);
|
||||
held = new Dictionary<string, EconomyFault>(StringComparer.Ordinal);
|
||||
foreach (var fault in EconomyChecks.Check(cache).Faults) held[Identity(fault)] = fault;
|
||||
recipesHeld = new HashSet<string>(cache.GetAll<RecipeData>().Select(r => cache.RefOf(r).Key.Value), StringComparer.Ordinal);
|
||||
original = Records(cache);
|
||||
}
|
||||
catch (Exception e) when (e is UnauthorizedAccessException || e is IOException)
|
||||
{
|
||||
output.WriteLine(Held("read", e));
|
||||
return 1;
|
||||
}
|
||||
var touched = new List<string>();
|
||||
int written;
|
||||
using (var cache = AetheriaStores.Open(trial, catalogWritable: true, registry: registry, kindlessStation: warning => output.WriteLine("warning: " + warning)))
|
||||
|
|
@ -214,6 +221,10 @@ public static class EconomyCommand
|
|||
}
|
||||
}
|
||||
|
||||
// Something holds Aetheria.cc open: a refusal that names the holder to close, never an exception.
|
||||
private static string Held(string verb, Exception e) =>
|
||||
$"The batches are refused: Aetheria.cc could not be {verb} ({e.Message}). Something holds the file: close Unity and any other editor or tool that has it open, then run again. The catalog is unchanged.";
|
||||
|
||||
// A fault is the same fault by rule, record and message, so fixing one fault on a record cannot hide a new one there. L-productive
|
||||
// is one fault per cycle: its identity is the cycle's member set, and the same cycle with a higher radius is a worse fault.
|
||||
private static string Identity(EconomyFault fault) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue