diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bb5747 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ + +# user spesific files +.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +.vscode/* \ No newline at end of file diff --git a/BWR.ConsoleApp/Program.cs b/BWR.ConsoleApp/Program.cs new file mode 100644 index 0000000..93ea96d --- /dev/null +++ b/BWR.ConsoleApp/Program.cs @@ -0,0 +1,57 @@ +// MyBWRSimulator.ConsoleApp/Program.cs +namespace MyBWRSimulator.ConsoleApp +{ + using MyBWRSimulator.Core.Simulation; + using MyBWRSimulator.Core.Components.Concrete; // Needed for specific component instantiation if not using config + using MyBWRSimulator.Core.Ports; // Needed for specific port instantiation if not using config + using System; + using System.IO; + + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Starting BWR Simulator Console Application..."); + + var simulator = new Simulator(); + + // Define the path to your configuration file + // Adjust this path as needed for your project structure + string configFilePath = Path.Combine(Directory.GetCurrentDirectory(), "config", "PlantConfig.json"); + + // Option 1: Load configuration from JSON file + //simulator.LoadPlantConfiguration(configFilePath); + + // Option 2 (Alternative for testing without config file): Manually create components and connect them + + var reactor = new ReactorCore("RC001", "Main Reactor Core", 4); + var pump = new Pump("PMP001", "Recirculation Pump", 100.0, 50000.0); // maxFlow, maxHeadPressure + var pipe = new Pipe("PIP001", "Hot Leg Pipe", 20.0, 0.5); // length, diameter + var tank = new FluidTank("TK001", "Condensate Tank", 500.0, 1, 1, 250.0); // maxVol, numInlets, numOutlets, initialVol + + simulator.AddComponent(reactor); + simulator.AddComponent(pump); + simulator.AddComponent(pipe); + simulator.AddComponent(tank); + + // Establish connections manually (example) + // Note: You'd need to access the specific ports of each component. + // This is simplified and assumes direct port access for brevity. + // In a real scenario, you'd use GetFluidPorts() etc. + // For example: pump.GetThermalPorts().First(p => p.ID.Contains("PowerIn")).Connect(powerSource.GetThermalPorts().First(p => p.ID.Contains("PowerOut"))); + + // Example of manual connection (requires casting and knowing port IDs) + // This is simplified and might break if port IDs change. + // The ConfigurationLoader handles this more robustly. + reactor.GetFluidPortById("FeedwaterInlet").Connect(pipe.GetFluidPortById("Inlet")); + pipe.GetFluidPortById("Outlet").Connect(pump.GetFluidPortById("Inlet")); + pump.GetFluidPortById("Outlet").Connect(tank.GetFluidPortById("Outlet")); // Tank has Inlet1, Inlet2 etc. + + // Run the simulation for 60 seconds with 1-second time steps + simulator.RunSimulation(60.0, 1.0); + + Console.WriteLine("\nSimulation application finished. Press any key to exit."); + Console.ReadKey(); + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Abstract/Component.cs b/BWR.core/Components/Abstract/Component.cs new file mode 100644 index 0000000..30e079e --- /dev/null +++ b/BWR.core/Components/Abstract/Component.cs @@ -0,0 +1,154 @@ +namespace MyBWRSimulator.Core.Components.Abstract +{ + using MyBWRSimulator.Core.Interfaces; + using MyBWRSimulator.Core.Ports; + using System.Collections.Generic; + using System.Linq; // Needed for .ToList() + + /// + /// Abstract base class for all simulated physical components. + /// Implements ISimulatable, IFluidConnectable, and IThermalConnectable. + /// + public abstract class Component : ISimulatable, IFluidConnectable, IThermalConnectable + { + /// + /// Unique identifier for the component. + /// + public string ID { get; protected set; } + + /// + /// Human-readable name of the component. + /// + public string Name { get; protected set; } + + /// + /// Detailed description of the component. + /// + public string Description { get; protected set; } + + /// + /// An integer value indicating the order in which components should be updated. + /// Lower numbers mean higher priority (updated first). + /// + public int UpdatePriority { get; protected set; } + + /// + /// Indicates if the component is receiving sufficient Thermal power. + /// + public bool IsPowered { get; protected set; } + + /// + /// Collection of fluid ports belonging to this component, accessible by their ID. + /// + protected Dictionary FluidPorts { get; set; } = new Dictionary(); + + /// + /// Collection of Thermal ports belonging to this component, accessible by their ID. + /// + protected Dictionary ThermalPorts { get; set; } = new Dictionary(); + + /// + /// Constructor for the Component base class. + /// + /// Unique identifier. + /// Human-readable name. + /// Description of the component. + /// Priority for simulation updates. + protected Component(string id, string name, string description, int updatePriority) + { + ID = id; + Name = name; + Description = description; + UpdatePriority = updatePriority; + } + + /// + /// Initializes the component. Called once when the simulation starts or component is added. + /// + public virtual void Initialize() + { + Console.WriteLine($"Initializing {Name} ({ID})..."); + // Placeholder for component-specific initialization logic + } + + /// + /// Shuts down the component. Called once when the simulation ends or component is removed. + /// + public virtual void Shutdown() + { + Console.WriteLine($"Shutting down {Name} ({ID})..."); + // Placeholder for component-specific cleanup logic + } + + /// + /// Abstract method to update the component's state over a time step. + /// Must be implemented by concrete component classes. + /// + /// The time elapsed. + public abstract void Update(double deltaTime); + + /// + /// Returns a list of all fluid ports for this component. + /// + /// A list of FluidPort objects. + public List GetFluidPorts() => FluidPorts.Values.ToList(); + + /// + /// Gets a fluid port by its unique ID. + /// + /// The ID of the fluid port. + /// The FluidPort object if found, otherwise null. + public FluidPort GetFluidPortById(string id) + { + FluidPorts.TryGetValue(id, out FluidPort port); + return port; + } + + /// + /// Returns a list of all Thermal ports for this component. + /// + /// A list of Thermal objects. + public List GetThermalPorts() => ThermalPorts.Values.ToList(); + + /// + /// Gets an Thermal port by its unique ID. + /// + /// The ID of the Thermal port. + /// The ThermalPort object if found, otherwise null. + public ThermalPort GetThermalPortById(string id) + { + ThermalPorts.TryGetValue(id, out ThermalPort port); + return port; + } + + /// + /// Helper method to add a fluid port to this component. + /// + /// The FluidPort to add. + /// Thrown if a port with the same ID already exists. + protected void AddFluidPort(FluidPort port) + { + if (FluidPorts.ContainsKey(port.ID)) + { + throw new ArgumentException($"A fluid port with ID '{port.ID}' already exists on component '{Name}' ({ID})."); + } + port.ParentComponent = this; + FluidPorts.Add(port.ID, port); + } + + /// + /// Helper method to add an Thermal port to this component. + /// + /// The ThermalPort to add. + /// Thrown if an Thermal port with the same ID already exists. + protected void AddThermalPort(ThermalPort port) + { + if (ThermalPorts.ContainsKey(port.ID)) + { + throw new ArgumentException($"An Thermal port with ID '{port.ID}' already exists on component '{Name}' ({ID})."); + } + port.ParentComponent = this; + ThermalPorts.Add(port.ID, port); + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Abstract/Port.cs b/BWR.core/Components/Abstract/Port.cs new file mode 100644 index 0000000..c93b380 --- /dev/null +++ b/BWR.core/Components/Abstract/Port.cs @@ -0,0 +1,77 @@ +// MyBWRSimulator.Core/Components/Abstract/Port.cs +namespace MyBWRSimulator.Core.Components.Abstract +{ + /// + /// Abstract base class for all types of connection points on components. + /// + public abstract class Port + { + /// + /// Unique ID for the port (e.g., "Inlet1", "OutletA"). + /// + public string ID { get; protected set; } + + /// + /// A reference to the Port it's connected to. Null if not connected. + /// + public Port ConnectedPort { get; protected set; } + + /// + /// A reference back to the component this port belongs to. + /// + public Component ParentComponent { get; internal set; } // Internal set by AddPort methods + + /// + /// Constructor for the Port base class. + /// + /// Unique identifier for the port. + protected Port(string id) + { + ID = id; + } + + /// + /// Establishes a connection between this port and another port. + /// + /// The port to connect to. + public virtual void Connect(Port otherPort) + { + if (otherPort == null) + { + Console.WriteLine($"Error: Cannot connect {ID} to null port."); + return; + } + if (ConnectedPort != null && ConnectedPort != otherPort) + { + Console.WriteLine($"Warning: {ID} is already connected to {ConnectedPort.ID}. Disconnecting first."); + Disconnect(); + } + + ConnectedPort = otherPort; + // For bidirectional connection, the other port should also connect to this one + if (otherPort.ConnectedPort != this) + { + otherPort.Connect(this); + } + Console.WriteLine($"Port {ID} connected to {otherPort.ID}."); + } + + /// + /// Breaks the connection of this port. + /// + public virtual void Disconnect() + { + if (ConnectedPort != null) + { + Port oldConnectedPort = ConnectedPort; + ConnectedPort = null; + // Ensure the other port also disconnects from this one + if (oldConnectedPort.ConnectedPort == this) + { + oldConnectedPort.Disconnect(); + } + Console.WriteLine($"Port {ID} disconnected from {oldConnectedPort.ID}."); + } + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Concrete/FluidTank.cs b/BWR.core/Components/Concrete/FluidTank.cs new file mode 100644 index 0000000..6cc0881 --- /dev/null +++ b/BWR.core/Components/Concrete/FluidTank.cs @@ -0,0 +1,67 @@ +// MyBWRSimulator.Core/Components/Concrete/FluidTank.cs +namespace MyBWRSimulator.Core.Components.Concrete +{ + using MyBWRSimulator.Core.Components.Abstract; + //using MyBWRSimulator.Core.Components.Enums; + using MyBWRSimulator.Core.Ports; + using System; + using System.Collections.Generic; + + /// + /// Represents a Fluid Tank component in the BWR simulation. + /// + public class FluidTank : Component + { + public double MaxVolume { get; private set; } // Maximum capacity of the tank (m^3) + public double CurrentVolume { get; private set; } // Current fluid volume in the tank (m^3) + public double CurrentLevel => CurrentVolume / MaxVolume; // 0.0 to 1.0 (0% to 100%) + public double ContentsTemperature { get; private set; } // Average temperature of fluid in tank + + public FluidPort Inlet => GetFluidPortById("Inlet"); + public FluidPort Outlet => GetFluidPortById("Outlet"); + + public FluidTank(string id, string name, double maxVolume, int numInlets, int numOutlets, double initialVolume = 0.0, double initialTemp = 293.15) + : base(id, name, $"A fluid tank with max volume {maxVolume} m^3.", 5) // Priority 5 (updates last) + { + MaxVolume = maxVolume; + CurrentVolume = Math.Clamp(initialVolume, 0.0, MaxVolume); + ContentsTemperature = initialTemp; + + + AddFluidPort(new FluidPort("Inlet")); + AddFluidPort(new FluidPort("Outlet")); + } + + public override void Update(double deltaTime) + { + double FlowVolume = 0.0; + double InletEnergy = 0.0; // For temperature calculation + + FlowVolume += Inlet.FlowRate * deltaTime; // FlowRate is volume/time, so FlowRate * deltaTime = volume + InletEnergy += Inlet.FlowRate * Inlet.Temperature * deltaTime; // Simple energy balance + + // Sum outgoing flow (assuming outlets draw based on their connected components) + // For a tank, outlets typically "demand" flow, and the tank supplies it if available. + // For simplicity here, we'll assume outlets simply remove fluid. + // In a real system, outlet flow depends on downstream pressure and tank level. + FlowVolume -= Outlet.FlowRate * deltaTime; + + + // Update volume + double newVolume = CurrentVolume + FlowVolume; + CurrentVolume = Math.Clamp(newVolume, 0.0, MaxVolume); + + // Update outlet port properties (e.g., pressure based on level) + // This is a simplification; in reality, outlet pressure also depends on downstream pressure + double pressureFromLevel = CurrentLevel * 100000; // Example: 100kPa at full level + + Outlet.Pressure = pressureFromLevel; // Or a more complex calculation + Outlet.Temperature = ContentsTemperature; + // Mirror values to connected ports + Outlet.MirrorToConnectedPort(); + + + Console.WriteLine($"{Name} ({ID}) - Volume: {CurrentVolume:F2} m^3 ({CurrentLevel * 100:F1}%), Temp: {ContentsTemperature:F1} K"); + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Concrete/PressureVessel.cs b/BWR.core/Components/Concrete/PressureVessel.cs new file mode 100644 index 0000000..e69de29 diff --git a/BWR.core/Components/Concrete/Pump.cs b/BWR.core/Components/Concrete/Pump.cs new file mode 100644 index 0000000..fd051de --- /dev/null +++ b/BWR.core/Components/Concrete/Pump.cs @@ -0,0 +1,74 @@ +// MyBWRSimulator.Core/Components/Concrete/Pump.cs +namespace MyBWRSimulator.Core.Components.Concrete +{ + using MyBWRSimulator.Core.Components.Abstract; + using MyBWRSimulator.Core.Ports; + using System; + using System.Collections.Generic; + + /// + /// Represents a Pump component in the BWR simulation. + /// + public class Pump : Component + { + public double PumpSpeed { get; set; } // 0.0 to 1.0 (0% to 100%) + public double MaxFlowRate { get; private set; } // Max flow at 100% speed + public double MaxHeadPressure { get; private set; } // Max pressure at 100% speed + + // Publicly expose ports for easier named access if needed, but GetFluidPortById is preferred + public FluidPort Inlet => GetFluidPortById("Inlet"); + public FluidPort Outlet => GetFluidPortById("Outlet"); + + + public Pump(string id, string name, double maxFlowRate, double maxHeadPressure) + : base(id, name, $"A pump component with max flow {maxFlowRate} and max head {maxHeadPressure}.", 1) // Priority 1 + { + MaxFlowRate = maxFlowRate; + MaxHeadPressure = maxHeadPressure; + PumpSpeed = 0.0; // Initially off + + // Use descriptive IDs for ports + AddFluidPort(new FluidPort("Inlet")); + AddFluidPort(new FluidPort("Outlet")); + } + + public override void Update(double deltaTime) + { + if (PumpSpeed > 0) + { + // Simple pump model: Flow proportional to speed, pressure based on head + double actualFlowRate = PumpSpeed * MaxFlowRate; + double actualPressureIncrease = PumpSpeed * MaxHeadPressure; + + // Read from inlet + double inletPressure = Inlet.Pressure; + double inletTemperature = Inlet.Temperature; + + // Calculate outlet properties + Outlet.FlowRate = actualFlowRate; + Outlet.Pressure = inletPressure + actualPressureIncrease; + Outlet.Temperature = inletTemperature; // Assuming no temperature change by pump + + // Mirror values to connected ports + Outlet.MirrorToConnectedPort(); + + Console.WriteLine($"{Name} ({ID}) - Flow: {actualFlowRate:F2} kg/s, Outlet P: {Outlet.Pressure:F2} Pa"); + } + else + { + // If not powered or off, no flow + Outlet.FlowRate = 0.0; + Outlet.Pressure = Inlet.Pressure; // Outlet pressure equals inlet if no pumping + Outlet.Temperature = Inlet.Temperature; + Outlet.MirrorToConnectedPort(); + Console.WriteLine($"{Name} ({ID}) - Off/No Power."); + } + } + + public void SetPumpSpeed(double speed) + { + PumpSpeed = Math.Clamp(speed, 0.0, 1.0); + Console.WriteLine($"{Name} ({ID}) - Pump speed set to {PumpSpeed * 100:F0}%."); + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Concrete/ReactorCore.cs b/BWR.core/Components/Concrete/ReactorCore.cs new file mode 100644 index 0000000..3afe4ce --- /dev/null +++ b/BWR.core/Components/Concrete/ReactorCore.cs @@ -0,0 +1,25 @@ +// MyBWRSimulator.Core/Components/Concrete/Pump.cs +namespace MyBWRSimulator.Core.Components.Concrete +{ + using MyBWRSimulator.Core.Components.Abstract; + //using MyBWRSimulator.Core.Components.Enums; + using MyBWRSimulator.Core.Ports; + using System; + using System.Collections.Generic; + + /// + /// Represents a Pump component in the BWR simulation. + /// + public class ReactorCore : Component + { + public ReactorCore(string id, string name, int priority) + : base(id, name, "Simulates all fuel rods and control rods", priority) + { + AddFluidPort(new FluidPort("FeedwaterInlet")); + } + public override void Update(double dt) + { + + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Concrete/pipe.cs b/BWR.core/Components/Concrete/pipe.cs new file mode 100644 index 0000000..91ca8ef --- /dev/null +++ b/BWR.core/Components/Concrete/pipe.cs @@ -0,0 +1,63 @@ +// MyBWRSimulator.Core/Components/Concrete/Pipe.cs +namespace MyBWRSimulator.Core.Components.Concrete +{ + using MyBWRSimulator.Core.Components.Abstract; + using MyBWRSimulator.Core.Ports; + using System; + using System.Collections.Generic; + + /// + /// Represents a Pipe component in the BWR simulation. + /// + public class Pipe : Component + { + public double Length { get; private set; } // Length of the pipe (m) + public double Diameter { get; private set; } // Inner diameter of the pipe (m) + public double Roughness { get; private set; } = 0.000045; // Example roughness for steel (m) + + // Publicly expose ports for easier named access if needed + public FluidPort Inlet => GetFluidPortById("Inlet"); + public FluidPort Outlet => GetFluidPortById("Outlet"); + + public Pipe(string id, string name, double length, double diameter) + : base(id, name, $"A pipe of length {length}m and diameter {diameter}m.", 4) // Priority 4 + { + Length = length; + Diameter = diameter; + + // Use descriptive IDs for ports + AddFluidPort(new FluidPort("Inlet")); + AddFluidPort(new FluidPort("Outlet")); + } + + public override void Update(double deltaTime) + { + // Read properties from the inlet port (which should have been updated by upstream component) + double inletFlowRate = Inlet.FlowRate; + double inletPressure = Inlet.Pressure; + double inletTemperature = Inlet.Temperature; + + // Simple pressure drop calculation (e.g., Darcy-Weisbach or simpler linear drop) + // This is very simplified. For high fidelity, you'd need fluid properties (density, viscosity). + double pressureDrop = 0.0; + if (inletFlowRate > 0) + { + // A very basic linear pressure drop for demonstration + pressureDrop = (inletFlowRate * Length * 10.0) / (Diameter * Diameter); + } + + // Simple heat loss/gain (e.g., based on ambient temperature, not modeled here) + double outletTemperature = inletTemperature; // Assuming no heat loss for now + + // Update outlet port properties + Outlet.FlowRate = inletFlowRate; + Outlet.Pressure = inletPressure - pressureDrop; + Outlet.Temperature = outletTemperature; + + // Mirror values to connected ports + Outlet.MirrorToConnectedPort(); + + Console.WriteLine($"{Name} ({ID}) - Flow: {inletFlowRate:F2} kg/s, Pressure Drop: {pressureDrop:F2} Pa"); + } + } +} \ No newline at end of file diff --git a/BWR.core/Components/Enums/FluidPortType.cs b/BWR.core/Components/Enums/FluidPortType.cs new file mode 100644 index 0000000..8192ec5 --- /dev/null +++ b/BWR.core/Components/Enums/FluidPortType.cs @@ -0,0 +1,14 @@ +// MyBWRSimulator.Core/Components/Enums/FluidPortType.cs +/* +namespace MyBWRSimulator.Core.Components.Enums +{ + /// + /// Represents the type of a fluid port (e.g., inlet or outlet). + /// + public enum FluidPortType + { + Inlet, + Outlet + } +} +*/ \ No newline at end of file diff --git a/BWR.core/Data/ConfigurationLoader.cs b/BWR.core/Data/ConfigurationLoader.cs new file mode 100644 index 0000000..5a619eb --- /dev/null +++ b/BWR.core/Data/ConfigurationLoader.cs @@ -0,0 +1,167 @@ +// MyBWRSimulator.Core/Data/ConfigurationLoader.cs +namespace MyBWRSimulator.Core.Data +{ + using MyBWRSimulator.Core.Components.Abstract; + using MyBWRSimulator.Core.Components.Concrete; + using MyBWRSimulator.Core.Ports; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + + /// + /// Helper class for deserializing component and connection configurations. + /// + public class ComponentConfig + { + public string Type { get; set; } + public string ID { get; set; } + public string Name { get; set; } + public int UpdatePriority { get; set; } + public JsonElement InitialState { get; set; } // Use JsonElement to handle dynamic properties + } + + /// + /// Helper class for deserializing connection configurations. + /// + public class PortReference + { + public string ComponentID { get; set; } + public string PortID { get; set; } + } + + /// + /// Overall configuration structure. + /// + public class PlantConfiguration + { + public List Components { get; set; } + public List Connections { get; set; } + } + + /// + /// Helper class for deserializing connection configurations. + /// + public class ConnectionConfig + { + public PortReference PortA { get; set; } + public PortReference PortB { get; set; } + } + + + /// + /// Handles loading simulation configuration from a JSON file. + /// + public class ConfigurationLoader + { + /// + /// Loads components and establishes connections from a JSON configuration file. + /// + /// The path to the JSON configuration file. + /// An output list to populate with instantiated components. + public static void LoadConfiguration(string filePath, out List components) + { + components = new List(); + if (!File.Exists(filePath)) + { + Console.WriteLine($"Error: Configuration file not found at {filePath}"); + return; + } + + try + { + string jsonString = File.ReadAllText(filePath); + var config = JsonSerializer.Deserialize(jsonString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + if (config?.Components == null) + { + Console.WriteLine("Error: No components found in configuration."); + return; + } + + // 1. Instantiate Components + foreach (var compConfig in config.Components) + { + Component newComponent = null; + switch (compConfig.Type) + { + case "Pump": + // Example: Deserialize specific properties for Pump + var pumpProps = compConfig.InitialState.Deserialize>(); + newComponent = new Pump(compConfig.ID, compConfig.Name, pumpProps["MaxFlowRate"], pumpProps["MaxHeadPressure"]); + if (pumpProps.TryGetValue("PumpSpeed", out double pumpSpeed)) + { + ((Pump)newComponent).SetPumpSpeed(pumpSpeed); + } + break; + case "FluidTank": + // Example: Deserialize specific properties for FluidTank + var tankProps = compConfig.InitialState.Deserialize>(); + newComponent = new FluidTank(compConfig.ID, compConfig.Name, tankProps["MaxVolume"], (int)tankProps["NumInlets"], (int)tankProps["NumOutlets"], tankProps["InitialVolume"]); + break; + case "Pipe": + var pipeProps = compConfig.InitialState.Deserialize>(); + newComponent = new Pipe(compConfig.ID, compConfig.Name, pipeProps["Length"], pipeProps["Diameter"]); + break; + case "ReactorCore": + newComponent = new ReactorCore(compConfig.ID, compConfig.Name, compConfig.UpdatePriority); + // ReactorCore might have no specific initial state beyond base properties + break; + // Add more component types here + default: + Console.WriteLine($"Warning: Unknown component type '{compConfig.Type}' for ID '{compConfig.ID}'. Skipping."); + break; + } + + if (newComponent != null) + { + components.Add(newComponent); + newComponent.Initialize(); // Initialize after creation + } + } + + // 2. Establish Connections + if (config.Connections != null) + { + foreach (var connConfig in config.Connections) + { + var compA = components.FirstOrDefault(c => c.ID == connConfig.PortA.ComponentID); + var compB = components.FirstOrDefault(c => c.ID == connConfig.PortB.ComponentID); + + if (compA == null || compB == null) + { + Console.WriteLine($"Warning: Could not find one or both components for connection: {connConfig.PortA.ComponentID}.{connConfig.PortA.PortID} <-> {connConfig.PortB.ComponentID}.{connConfig.PortB.PortID}"); + continue; + } + + // Use the new GetPortById methods for more robust lookup + Port portA = compA.GetFluidPortById(connConfig.PortA.PortID) as Port ?? + compA.GetThermalPortById(connConfig.PortA.PortID) as Port; + + Port portB = compB.GetFluidPortById(connConfig.PortB.PortID) as Port ?? + compB.GetThermalPortById(connConfig.PortB.PortID) as Port; + + if (portA != null && portB != null) + { + portA.Connect(portB); + } + else + { + Console.WriteLine($"Warning: Could not find one or both ports for connection: {connConfig.PortA.ComponentID}.{connConfig.PortA.PortID} <-> {connConfig.PortB.ComponentID}.{connConfig.PortB.PortID}"); + } + } + } + } + catch (JsonException ex) + { + Console.WriteLine($"Error parsing JSON configuration: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"An unexpected error occurred during configuration loading: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/BWR.core/Interfaces/IFluidConnectable.cs b/BWR.core/Interfaces/IFluidConnectable.cs new file mode 100644 index 0000000..a631b53 --- /dev/null +++ b/BWR.core/Interfaces/IFluidConnectable.cs @@ -0,0 +1,18 @@ +// MyBWRSimulator.Core/Interfaces/IFluidConnectable.cs +namespace MyBWRSimulator.Core.Interfaces +{ + using MyBWRSimulator.Core.Ports; + using System.Collections.Generic; + + /// + /// Defines the contract for components that have fluid connections. + /// + public interface IFluidConnectable + { + /// + /// Gets a list of fluid ports associated with this component. + /// + /// A list of FluidPort objects. + List GetFluidPorts(); + } +} \ No newline at end of file diff --git a/BWR.core/Interfaces/ISimulatable.cs b/BWR.core/Interfaces/ISimulatable.cs new file mode 100644 index 0000000..f11085f --- /dev/null +++ b/BWR.core/Interfaces/ISimulatable.cs @@ -0,0 +1,15 @@ +// MyBWRSimulator.Core/Interfaces/ISimulatable.cs +namespace MyBWRSimulator.Core.Interfaces +{ + /// + /// Defines the contract for any object that can participate in the simulation's time-stepping. + /// + public interface ISimulatable + { + /// + /// Updates the state of the object over a given time step. + /// + /// The time elapsed since the last update. + void Update(double deltaTime); + } +} \ No newline at end of file diff --git a/BWR.core/Interfaces/IThermalConnectable.cs b/BWR.core/Interfaces/IThermalConnectable.cs new file mode 100644 index 0000000..0270dab --- /dev/null +++ b/BWR.core/Interfaces/IThermalConnectable.cs @@ -0,0 +1,18 @@ +// MyBWRSimulator.Core/Interfaces/IThermalConnectable.cs +namespace MyBWRSimulator.Core.Interfaces +{ + using MyBWRSimulator.Core.Ports; + using System.Collections.Generic; + + /// + /// Defines the contract for components that have Thermal connections. + /// + public interface IThermalConnectable + { + /// + /// Gets a list of Thermal ports associated with this component. + /// + /// A list of ThermalPort objects. + List GetThermalPorts(); + } +} \ No newline at end of file diff --git a/BWR.core/Ports/FluidPort.cs b/BWR.core/Ports/FluidPort.cs new file mode 100644 index 0000000..5a9927c --- /dev/null +++ b/BWR.core/Ports/FluidPort.cs @@ -0,0 +1,71 @@ +// MyBWRSimulator.Core/Ports/FluidPort.cs +namespace MyBWRSimulator.Core.Ports +{ + //using MyBWRSimulator.Core.Components.Enums; + using MyBWRSimulator.Core.Components.Abstract; + + /// + /// Represents a fluid connection point on a component. + /// + public class FluidPort : Port + { + /// + /// Current fluid flow rate through this port (e.g., kg/s). + /// + public double FlowRate { get; set; } + + /// + /// Current fluid pressure at this port (e.g., Pa). + /// + public double Pressure { get; set; } + + /// + /// Current fluid temperature at this port (e.g., K or C). + /// + public double Temperature { get; set; } + + /// + /// Current volume of fluid within the port itself (optional, for more detailed modeling). + /// + public double CurrentVolume { get; set; } + + /// + /// Constructor for a FluidPort. + /// + /// Unique identifier for the port. + /// The type of fluid port (Inlet or Outlet). + public FluidPort(string id) : base(id) + { + FlowRate = 0.0; + Pressure = 0.0; + Temperature = 0.0; + CurrentVolume = 0.0; + } + + /// + /// Connects this fluid port to another fluid port. + /// Includes basic type checking. + /// + /// The port to connect to. + public override void Connect(Port otherPort) + { + base.Connect(otherPort); + } + + /// + /// Mirrors fluid properties from this port to its connected port. + /// This is for the direct property assignment data flow model. + /// + public void MirrorToConnectedPort() + { + if (ConnectedPort is FluidPort connectedFluidPort) + { + connectedFluidPort.FlowRate = this.FlowRate; + connectedFluidPort.Pressure = this.Pressure; + connectedFluidPort.Temperature = this.Temperature; + // Optionally, mirror CurrentVolume if applicable + // connectedFluidPort.CurrentVolume = this.CurrentVolume; + } + } + } +} \ No newline at end of file diff --git a/BWR.core/Ports/ThermalPort.cs b/BWR.core/Ports/ThermalPort.cs new file mode 100644 index 0000000..6985c4c --- /dev/null +++ b/BWR.core/Ports/ThermalPort.cs @@ -0,0 +1,68 @@ +// MyBWRSimulator.Core/Ports/ThermalPort.cs +namespace MyBWRSimulator.Core.Ports +{ + using MyBWRSimulator.Core.Components.Abstract; + + /// + /// Represents an Thermal connection point on a component. + /// + public class ThermalPort : Port + { + /// + /// Current voltage at this port (e.g., Volts). + /// + public double Voltage { get; set; } + + /// + /// Current amperage at this port (e.g., Amperes). + /// + public double Amperage { get; set; } + + /// + /// True if this port is actively supplying power. + /// + public bool IsSupplyingPower { get; set; } + + /// + /// Constructor for an ThermalPort. + /// + /// Unique identifier for the port. + public ThermalPort(string id) : base(id) + { + Voltage = 0.0; + Amperage = 0.0; + IsSupplyingPower = false; + } + + /// + /// Connects this Thermal port to another Thermal port. + /// Includes basic type checking. + /// + /// The port to connect to. + public override void Connect(Port otherPort) + { + if (otherPort is ThermalPort) + { + base.Connect(otherPort); + } + else + { + Console.WriteLine($"Error: Cannot connect ThermalPort {ID} to non-ThermalPort {otherPort.ID}."); + } + } + + /// + /// Mirrors Thermal properties from this port to its connected port. + /// This is for the direct property assignment data flow model. + /// + public void MirrorToConnectedPort() + { + if (ConnectedPort is ThermalPort connectedThermalPort) + { + connectedThermalPort.Voltage = this.Voltage; + connectedThermalPort.Amperage = this.Amperage; + connectedThermalPort.IsSupplyingPower = this.IsSupplyingPower; + } + } + } +} \ No newline at end of file diff --git a/BWR.core/Simulator.cs b/BWR.core/Simulator.cs new file mode 100644 index 0000000..be71899 --- /dev/null +++ b/BWR.core/Simulator.cs @@ -0,0 +1,141 @@ +// MyBWRSimulator.Core/Simulation/Simulator.cs +namespace MyBWRSimulator.Core.Simulation +{ + using MyBWRSimulator.Core.Components.Abstract; + using MyBWRSimulator.Core.Data; + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// The orchestrator of the simulation. Manages components, runs the simulation loop, and provides data. + /// + public class Simulator + { + private List _components; + private double _simulationTime; + private bool _isPaused; + + public Simulator() + { + _components = new List(); + _simulationTime = 0.0; + _isPaused = false; + } + + /// + /// Adds a component to the simulation. + /// + /// The component to add. + public void AddComponent(Component component) + { + if (component != null && !_components.Contains(component)) + { + _components.Add(component); + _components = _components.OrderBy(c => c.UpdatePriority).ToList(); // Re-sort after adding + component.Initialize(); + Console.WriteLine($"Component '{component.Name}' ({component.ID}) added."); + } + } + + /// + /// Removes a component from the simulation. + /// + /// The component to remove. + public void RemoveComponent(Component component) + { + if (component != null && _components.Remove(component)) + { + _components = _components.OrderBy(c => c.UpdatePriority).ToList(); // Re-sort after removing + component.Shutdown(); + Console.WriteLine($"Component '{component.Name}' ({component.ID}) removed."); + } + } + + /// + /// Loads components and connections from a configuration file. + /// + /// The path to the configuration JSON file. + public void LoadPlantConfiguration(string filePath) + { + ConfigurationLoader.LoadConfiguration(filePath, out _components); + _components = _components.OrderBy(c => c.UpdatePriority).ToList(); // Ensure sorted after load + Console.WriteLine($"Loaded { _components.Count} components from configuration."); + } + + /// + /// Runs the simulation for a specified duration with a given time step. + /// + /// Total simulation duration (e.g., in seconds). + /// Time step for each update iteration (e.g., in seconds). + public void RunSimulation(double duration, double timeStep) + { + Console.WriteLine($"Starting simulation for {duration} seconds with a time step of {timeStep} seconds..."); + _isPaused = false; + + while (_simulationTime < duration && !_isPaused) + { + Console.WriteLine($"\n--- Simulation Time: {_simulationTime:F2} s ---"); + foreach (var component in _components) + { + component.Update(timeStep); + } + _simulationTime += timeStep; + + // For console output, add a small delay to see updates + System.Threading.Thread.Sleep(100); + } + + if (_simulationTime >= duration) + { + Console.WriteLine("\nSimulation finished."); + } + else if (_isPaused) + { + Console.WriteLine("\nSimulation paused."); + } + + // Call shutdown for all components at the end of the simulation + foreach (var component in _components) + { + component.Shutdown(); + } + } + + /// + /// Pauses the simulation. + /// + public void PauseSimulation() + { + _isPaused = true; + } + + /// + /// Resumes the simulation. + /// + public void ResumeSimulation() + { + _isPaused = false; + } + + /// + /// Gets current simulation data from all components. + /// (Placeholder - you'll define the structure of this data more concretely later) + /// + /// A dictionary containing simulation data. + public Dictionary GetSimulationData() + { + var data = new Dictionary(); + data["SimulationTime"] = _simulationTime; + data["ComponentStates"] = _components.Select(c => new + { + c.ID, + c.Name + // Add specific properties for each component type here + // Example for Pump: (c as Pump)?.PumpSpeed + // Example for FluidTank: (c as FluidTank)?.CurrentVolume + }).ToList(); + return data; + } + } +} \ No newline at end of file diff --git a/BWR.core/Utilities/Constants.cs b/BWR.core/Utilities/Constants.cs new file mode 100644 index 0000000..e69de29 diff --git a/BWR_rewrite.csproj b/BWR_rewrite.csproj new file mode 100644 index 0000000..90c53ef --- /dev/null +++ b/BWR_rewrite.csproj @@ -0,0 +1,9 @@ + + + + Exe + net8.0 + enable + + + diff --git a/BWR_rewrite.sln b/BWR_rewrite.sln new file mode 100644 index 0000000..8dddf44 --- /dev/null +++ b/BWR_rewrite.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BWR_rewrite", "BWR_rewrite.csproj", "{06A4A2A6-D413-5904-24B0-D5788442CB13}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {06A4A2A6-D413-5904-24B0-D5788442CB13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06A4A2A6-D413-5904-24B0-D5788442CB13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06A4A2A6-D413-5904-24B0-D5788442CB13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06A4A2A6-D413-5904-24B0-D5788442CB13}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {504A20E3-1BAE-41B3-AC23-5F6EC8680BB7} + EndGlobalSection +EndGlobal diff --git a/Other.cs b/Other.cs new file mode 100644 index 0000000..9e282d6 --- /dev/null +++ b/Other.cs @@ -0,0 +1,27 @@ +using System; + +namespace BWR_simulator +{ + class PlantMngr + { + // simulation step all plant components + public void StepAll() + { + + } + + public void DisplayStats() + { + + } + } + + public static class Constants + { + public const double WATER_MOLAR_MASS = 0.018015; // kg/mol + public const double IDEAL_GAS_CONSTANT = 9425; + public const double WATER_LATENT_HEAT = 406500; // j*kg/k + public const double WATER_BOILING_POINT_NORM = 373; // temp in K the fluid boils at, under 1 atm + public const double WATER_HEAT_CAPACITY = 4182; // J/kg*K + } +} \ No newline at end of file diff --git a/PressureVessel.cs b/PressureVessel.cs new file mode 100644 index 0000000..55d50d8 --- /dev/null +++ b/PressureVessel.cs @@ -0,0 +1,97 @@ +using System; + +namespace BWR_simulator +{ + public class PressureVessel + { + public readonly double TOTAL_VOLUME; // Liters + private const double VESSEL_COOLING_COEFF = 1000; + private const double ROD_HEATING_COEFF = 2000; + + // fluid density coeffcients to calculate fluid density at different pressures and temps + private readonly double[] FDC = + [ + 999.83311, + 0.0752, + 0.0089, + 7.36413e-5, + 4.74639e-7, + 1.34888e-9, + 1.36e-11 + ]; + public double HotwellTemp { get; private set; } + + public double GasMass { get; private set; } + public double GasVolume { get; private set; } + public double GasDensity { get; private set; } + public double Pressure { get; private set; } + + public double FluidMass { get; private set; } + public double FluidDensity { get; private set; } + public double FluidVolume { get; private set; } + public double FluidTemp { get; private set; } + public double FluidBoilingPoint { get; private set; } + + public double FeedwaterFlow { get; private set; } + public double ReliefValvePos { get; private set; } + public double InputEnergy { get; private set; } + + public PressureVessel(double totalVolume = 9500, + double fluidTemp = 300, + double fluidMass = 6200, + double gasMass = 0) + { + TOTAL_VOLUME = totalVolume; + FluidTemp = fluidTemp; + FluidMass = fluidMass; + GasMass = gasMass; + + InputEnergy = 0; + ReliefValvePos = 0; + FeedwaterFlow = 0; + Pressure = 1; // calcualte initial pressure + FluidBoilingPoint = Constants.WATER_BOILING_POINT_NORM; // calculate boiling point givien initial pressure + FluidDensity = 1; + + HotwellTemp = 300; + CalculateDensity(); + + } + private void CalculateDensity() + { + FluidVolume = FluidMass / FluidDensity; + GasVolume = TOTAL_VOLUME - FluidVolume; + GasDensity = GasMass / GasVolume; + } + private void CalculateEnergyInput(double dt) + { + InputEnergy -= VESSEL_COOLING_COEFF * (HotwellTemp - FluidTemp) * dt; + InputEnergy += ROD_HEATING_COEFF * (600 - FluidTemp) * dt; + InputEnergy /= 1e6; + } + public void Step(double dt) + { + CalculateEnergyInput(dt); + FluidVolume += FeedwaterFlow * dt; + FluidBoilingPoint = Math.Pow((1/Constants.WATER_BOILING_POINT_NORM - (8.314 * Math.Log(Pressure))/40700), -1); + + FluidTemp += (InputEnergy * 1e6) / (FluidMass * Constants.WATER_HEAT_CAPACITY); + if (FluidTemp > FluidBoilingPoint) + { + InputEnergy = FluidMass * Constants.WATER_HEAT_CAPACITY * FluidTemp - FluidBoilingPoint; + FluidTemp = FluidBoilingPoint; + double d_vapour = InputEnergy / Constants.WATER_LATENT_HEAT; + FluidMass -= d_vapour; + GasMass += d_vapour; + + CalculateDensity(); + } + + Pressure = 1 + ((GasMass/Constants.WATER_MOLAR_MASS) * (Constants.IDEAL_GAS_CONSTANT * FluidTemp / TOTAL_VOLUME - FluidVolume)); + if (Pressure < 1) {Pressure = 1;} + + GasMass -= (Pressure - 1) * ReliefValvePos * dt; + if (GasMass < 0) {GasMass = 0;} + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..832ba64 --- /dev/null +++ b/Program.cs @@ -0,0 +1,22 @@ +/*using System; +using System.Collections.Generic; +using BWR_simulator; + +namespace BWR_siumulator +{ + public class Program + { + public static void Main(string[] args) + { + + PressureVessel p1 = new(); + + for(int i = 0; i < 10; i++) + { + Console.WriteLine($"Fluid Temp: {Math.Round(p1.FluidTemp - 273, 1)}"); + p1.Step(0.01); + System.Threading.Thread.Sleep(50); + } + } + } +}*/ diff --git a/Reactor.cs b/Reactor.cs new file mode 100644 index 0000000..1ed05ca --- /dev/null +++ b/Reactor.cs @@ -0,0 +1,162 @@ +namespace BWR_siumulator +{ + public class ThermalPort + { + public double Measurement { get; set; } + public double ValueToAffect { get; set; } + + public ThermalPort(double measurement, double valueToAffect) + { + Measurement = measurement; + ValueToAffect = valueToAffect; + } + } + + public class Reactor + { + // --- Core Physics and Material Constants --- + public const double CONTROL_ROD_SPEED = 1.0; + public const double DECAY_HEAT_FRACTION = 0.065; + public const double DECAY_HEAT_LAMBDA = 0.0077; + public const double BETA = 0.0067; + public const double LAMBDA_DECAY = 0.078; + public const double L_PROMPT_NEUTRON = 0.00002; + public const double FUEL_HEAT_CAPACITY = 2.8; + public const double HEAT_TRANSFER_COEFF = 0.5; + public const double ALPHA_SQRT_T_FUEL = -0.008; + public const double CONTROL_ROD_WORTH = -0.05; + public const double INTRINSIC_SOURCE = 0.25; + public const double COOLANT_TEMP = 300; // K + public const double STABLE_TEMP = 547; // K [The temperature at which the doppler effect has no effect on reactivity] + + // --- State Variables --- + public double Power { get; private set; } // MW + + // --- Inspect Variables --- + public double ReactivityDoppler { get; private set; } + public double ReactivityRod { get; private set; } + + public double FuelTemp { get; private set; } // K + public double HeatGenerated { get; private set; } + + public double Precursors { get; private set; } + public double DecayHeatPrecursors { get; private set; } + + public double ReactivityTotal { get; private set; } + public double ControlRodCurrentPosition { get; private set; } + public double ControlRodTargetPosition { get; set; } // Can be set externally + public double ReactorPeriod { get; private set; } + + public ThermalPort ThermalPort { get; private set; } + + public Reactor(double initialPower = 1.0, double fuelTemp = 300) + { + Power = initialPower; + FuelTemp = fuelTemp; + + // Initialize precursors and decay heat to be in equilibrium + Precursors = Power * BETA / (LAMBDA_DECAY * L_PROMPT_NEUTRON); + DecayHeatPrecursors = Power * DECAY_HEAT_FRACTION / DECAY_HEAT_LAMBDA; + + ReactivityTotal = 0.0; + ControlRodCurrentPosition = 100; + ControlRodTargetPosition = 100; + ReactorPeriod = double.PositiveInfinity; + + ThermalPort = new ThermalPort(FuelTemp, HeatGenerated); + } + private double GetReactivityFromRodPosition() + { + double positionRad = (ControlRodCurrentPosition / 100.0) * Math.PI; + double effectiveness = (1 - Math.Cos(positionRad)) / 2.0; + return CONTROL_ROD_WORTH * effectiveness; + } + + /// + /// Calculates reactivity from the fuel temperature. + /// + /// The reactivity due to fuel temperature (Doppler effect). + private double CalculateDopplerReactivity() + { + return ALPHA_SQRT_T_FUEL * (Math.Sqrt(FuelTemp) - Math.Sqrt(STABLE_TEMP)); + } + + /// + /// Advances the simulation by one time step, dt. + /// + /// The time step in seconds. + public void Step(double dt) + { + // --- UPDATE CONTROL ROD POSITION --- + if (ControlRodCurrentPosition != ControlRodTargetPosition) + { + double difference = ControlRodTargetPosition - ControlRodCurrentPosition; + double maxMove = CONTROL_ROD_SPEED * dt; + if (Math.Abs(difference) < maxMove) + { + ControlRodCurrentPosition = ControlRodTargetPosition; + } + else if (difference > 0) + { + ControlRodCurrentPosition += maxMove; + } + else + { + ControlRodCurrentPosition -= maxMove; + } + } + + // --- PHYSICS CALCULATION --- + // 1. Calculate Total Reactivity + ReactivityDoppler = CalculateDopplerReactivity(); + ReactivityRod = GetReactivityFromRodPosition(); + ReactivityTotal = ReactivityDoppler + ReactivityRod; + + // 2. Solve Point Kinetics + double powerOld = Power; + double dtLambda = dt * LAMBDA_DECAY; + double dtOverL = dt / L_PROMPT_NEUTRON; + double numerator = Power + Precursors * dtLambda / (1 + dtLambda) + dt * INTRINSIC_SOURCE; + double denominator = 1 - dtOverL * (ReactivityTotal - BETA) - + (dtOverL * BETA * dtLambda) / (1 + dtLambda); + Power = numerator / denominator; + + Precursors = (Precursors + dt * Power * BETA / L_PROMPT_NEUTRON) / (1 + dtLambda); + + // 3. Calculate Reactor Period + double powerChange = Power - powerOld; + if (Math.Abs(powerChange) > 1e-9 && Power > 1e-7) + { + ReactorPeriod = (Power * dt) / powerChange; + if (ReactorPeriod < -1500 || ReactorPeriod > 1500) + { + ReactorPeriod = double.PositiveInfinity; + } + } + else + { + ReactorPeriod = double.PositiveInfinity; + } + + // 4. Solve for Decay Heat + DecayHeatPrecursors = (DecayHeatPrecursors + dt * Power * DECAY_HEAT_FRACTION) / (1 + dt * DECAY_HEAT_LAMBDA); + + // 5. Solve Thermal-Hydraulics + double promptHeat = Power * (1 - DECAY_HEAT_FRACTION); + HeatGenerated = promptHeat + DecayHeatPrecursors; + //double heatRemoved = HEAT_TRANSFER_COEFF * (FuelTemp - COOLANT_TEMP); + //double dFuelTemp = (HeatGenerated - heatRemoved) / FUEL_HEAT_CAPACITY; + double dFuelTemp = HeatGenerated / FUEL_HEAT_CAPACITY; + FuelTemp += dFuelTemp * dt; + + // --- CLAMPING --- + if (Power < 0) Power = 0; + if (Precursors < 0) Precursors = 0; + if (DecayHeatPrecursors < 0) DecayHeatPrecursors = 0; + + // Update ThermalPort + ThermalPort.Measurement = FuelTemp; + ThermalPort.ValueToAffect = HeatGenerated; + } + } +} \ No newline at end of file