identification, simulation and … chapter 1 introduction the gerdau ameristeel mini-mill facility...
TRANSCRIPT
IDENTIFICATION, SIMULATION AND OPTIMIZATION OF AN ELECTRIC
ARC FURNACE
by
Billy W. Bryant Jr
A thesis submitted in partial fulfillment of therequirements for the Master of Science
degree in Electrical and Computer Engineeringin the Graduate College of
The University of Iowa
May 2006
Thesis Supervisor: Professor Er-Wei Bai
Graduate CollegeThe University of Iowa
Iowa City, Iowa
CERTIFICATE OF APPROVAL
MASTER’S THESIS
This is to certify that the Master’s thesis of
Billy W. Bryant Jr
has been approved by the Examining Committee for the thesis requirement for theMaster of Science degree in Electrical and Computer Engineering at the May 2006graduation.
Thesis committee:
Er-Wei Bai, Thesis Supervisor
Soura Dasgupta
Milan Sonka
TABLE OF CONTENTS
LIST OF TABLES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
LIST OF FIGURES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
CHAPTER
1 INTRODUCTION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Melting Process Description . . . . . . . . . . . . . . . . . . . . . 11.2 EAF Description . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.1 Burners Description . . . . . . . . . . . . . . . . . . . . . 71.2.2 Oxygen Lance Description . . . . . . . . . . . . . . . . . 7
1.3 Thesis Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.4 Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2 DATA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1 Data Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3 SYSTEM IDENTIFICATION . . . . . . . . . . . . . . . . . . . . . . 12
3.1 Melting Process Model Development . . . . . . . . . . . . . . . . 123.1.1 Electrical Energy Consumption . . . . . . . . . . . . . . . 143.1.2 Oxygen, Gas and Carbon Consumption . . . . . . . . . . 153.1.3 Electrode Consumption . . . . . . . . . . . . . . . . . . . 153.1.4 Percent Melted . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2 Model Accuracy . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4 SIMULATION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5 OPTIMIZATION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.1 Optimization Goal . . . . . . . . . . . . . . . . . . . . . . . . . . 425.2 Objective Function . . . . . . . . . . . . . . . . . . . . . . . . . . 425.3 Genetic Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.3.1 Initial Pool . . . . . . . . . . . . . . . . . . . . . . . . . . 435.3.2 Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . 445.3.3 Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . 445.3.4 Crossover . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.3.5 Mutation . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.3.6 GA Simulations . . . . . . . . . . . . . . . . . . . . . . . 46
6 RESULTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
ii
6.1 Model Performance Using New Data . . . . . . . . . . . . . . . . 526.2 Conclusion and Opportunities . . . . . . . . . . . . . . . . . . . 53
6.2.1 Metallurgical Effects . . . . . . . . . . . . . . . . . . . . . 546.2.2 Scrap and Layering . . . . . . . . . . . . . . . . . . . . . 546.2.3 Flux Additions . . . . . . . . . . . . . . . . . . . . . . . . 546.2.4 Burner Non-Linearities . . . . . . . . . . . . . . . . . . . 556.2.5 Genetic Algorithm . . . . . . . . . . . . . . . . . . . . . . 55
APPENDIX A. SOURCE CODE . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.1 Source Listing: filter rph data.rb . . . . . . . . . . . . . . . . . . 56A.2 Source Listing: build eaf data.rb . . . . . . . . . . . . . . . . . . 62A.3 Source Listing: build data stats.rb . . . . . . . . . . . . . . . . . 68A.4 Source Listing: build eaf lin.m . . . . . . . . . . . . . . . . . . . 75A.5 Source Listing: eval lin model.m . . . . . . . . . . . . . . . . . . 77A.6 Source Listing: config.rb . . . . . . . . . . . . . . . . . . . . . . . 80A.7 Source Listing: eaf sim.rb . . . . . . . . . . . . . . . . . . . . . . 84A.8 Source Listing: functions.rb . . . . . . . . . . . . . . . . . . . . . 87A.9 Source Listing: eaf.rb . . . . . . . . . . . . . . . . . . . . . . . . 98A.10 Source Listing: eaf profile.rb . . . . . . . . . . . . . . . . . . . . 116A.11 Source Listing: ga eaf sim.rb . . . . . . . . . . . . . . . . . . . . 122A.12 Source Listing: profile logger.rb . . . . . . . . . . . . . . . . . . . 126A.13 Simulator Output . . . . . . . . . . . . . . . . . . . . . . . . . . 129
REFERENCES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
iii
LIST OF TABLES
Table
2.1 Data Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1 KWH Regressor Correlation Strengths . . . . . . . . . . . . . . . . . . . 15
3.2 I2H Regressor Correlation Strengths . . . . . . . . . . . . . . . . . . . . 17
3.3 Percent Melted Regressor Correlation Strengths . . . . . . . . . . . . . . 18
3.4 Model Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
6.1 Model Percent Error on New Data . . . . . . . . . . . . . . . . . . . . . 52
6.2 Model Percent Error on Smaller Heats . . . . . . . . . . . . . . . . . . . 53
iv
LIST OF FIGURES
Figure
1.1 EAF Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 EAF Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 EAF Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.1 EAF Process Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 Charge KWH/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3 Heat KWH/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.4 Charge Gas/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.5 Heat Gas/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.6 Charge O2/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.7 Heat O2/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.8 Charge Carbon/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.9 Heat Carbon/Ton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.10 Power On Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.11 Power Off Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.12 I2h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.13 Percent Melted Residuals Histogram . . . . . . . . . . . . . . . . . . . . 32
3.14 KWH Residuals Histogram . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.15 I2H Residuals Histogram . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.16 O2 Residuals Histogram . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.17 Gas Residuals Histogram . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.18 Carbon Residuals Histogram . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.19 Percent Melted Residuals Autocorrelation . . . . . . . . . . . . . . . . . 39
v
3.20 Percent Melted Residuals Autocorrelation . . . . . . . . . . . . . . . . . 40
5.1 Fitness Vs. Mutation Rate, θ = 0.5 . . . . . . . . . . . . . . . . . . . . . 48
5.2 GA Performance (Balanced), θ = 0.5 . . . . . . . . . . . . . . . . . . . . 49
5.3 GA Performance, θ = 1.0 (TPH) . . . . . . . . . . . . . . . . . . . . . . 50
5.4 GA Performance, θ = 0.0 (Cost/Ton) . . . . . . . . . . . . . . . . . . . . 51
vi
1
CHAPTER 1
INTRODUCTION
The Gerdau Ameristeel mini-mill facility in Wilton Iowa is composed of a
scrap shredding facility, an 80 ton 25MW AC electric arc furnace (EAF), a 3 strand
continuous billet caster, a 14 stand rolling mill and approximately 350 employees. The
facility was commissioned in 1975. The facility produces over 300 finished products
varying in profile from flats to angles to squares to rebar and one of approximately
100 steel grades.
The Wilton facility is somewhat unique in that it does not contain a ladle re-
fining station (LRS). This means that all of the alloying required to achieve a specific
grade of steel must be accomplished in the EAF and ladle. This equipment configu-
ration makes steel production at this facility somewhat of an art. This fact coupled
with the large number of grades produced results in moderate process variance. In
terms of cost and performance, this equipment configuration can be advantageous
due to the energy and electrode savings from not using an LRS, and the time savings
by going directly from the EAF to casting.
1.1 Melting Process Description
The melting process is a batch process. One batch of steel is called a heat.
Process performance is measured by heat and usually averaged by the day, week,
month and year. The time between taps is the amount of time required to produce a
batch of liquid steel. Produced tons can be measured using charged scrap tons, liquid
tap tons, or cast billet tons. Tons in the context of this thesis always refer to charged
2
scrap tons. KWH/Ton can refer to total energy per ton from electrical, natural gas,
Carbon, Oxygen and other energy sources. In the context of this thesis, KWH refers
strictly to electrical energy. This thesis only addresses the melting process.
Scrap is brought into the melt shop inside of rail cars. An overhead crane is
used to load the scrap into a charging bucket. When the charge bucket has been
loaded, the loading crane picks up the charge bucket and charges (places) the scrap
into the EAF. Another unique feature of the Wilton facility is that the same crane is
used to load the scrap bucket and place the charge into the EAF. This is unfortunate
because at times it is difficult for the crane to load fast enough for the EAF, and
occasionally leads to operational delays.
The EAF is typically operated utilizing a 3 charge practice. The size and
number of charges can vary depending on factors such as scrap metal density, EAF
conditions, grade of steel being produced, and the operators on shift. The number
of charges varies between 3 and 5 depending on the noted conditions. The goal is to
always make a heat with three charges because of the higher production rate.
A melting cycle is composed of the following stages:
1. Furnace Turn Around (FTA) - The EAF is inspected and prepared for the next
heat.
2. Charge - Scrap is put into the EAF.
3. Melting - Power is turned on and the scrap is melted until the volume is suffi-
ciently reduced to fit the next charge into the EAF. The charge/melting cycle
3
Figure 1.1: EAF Diagram
is repeated as necessary. The melting cycle is composed of bore-in, melt-in and
refine.
4. Refine - Alloys are added to the liquid steel as necessary and the steel is brought
up to the required temperature.
5. Tap - The liquid steel is transfered from the EAF to a ladle for casting.
4
Figure 1.2: EAF Diagram
5
Figure 1.3: EAF Diagram
6
1.2 EAF Description
The EAF is composed of a 16ft shell, a 33.6 MVA transformer, aluminum cur-
rent conducting arms, 3 20 inch diameter graphite electrodes, and 4 sidewall burners
(see Figs 1.1-1.3). Two of the burners are Gas-Oxygen burners, the other two are
Gas-Oxygen burners with Carbon injection. The EAF is a balanced three phase
power delivery system. The phase currents/impedances are manipulated by moving
the electrodes up or down and changing the length of the arc.
There are two electrode control systems for the EAF with several modes of op-
eration available. The first system is used to move the electrodes via PLC controlled
hydraulics in order to maintain a current set-point. The system is responsible for
establishing the electric arc at the beginning of the melt cycle, and for re-establishing
the arc if it is extinguished by a scrap cave-in or a non-conductive part of the charge.
It can be configured to change the current level or impedance on a per-phase basis
according to a static schedule of limits (power program) indexed by Charge KWH or
Charge KWH/Ton. This system only has the capability of controlling the movement
of the electrodes. The system is responsible for measuring voltage and current pa-
rameters on the EAF and performing all of the calculations required to produce the
the complete electrical state of the EAF.
The second electrode control system is a supervisory type control system that
has the capability of dynamically calculating current/impedance set-points based on
fuzzy-logic based classification of the instantaneous point in the melting cycle (bore-
in, melting, refine). The system also has the capability of controlling the sidewall
7
burners according to a static schedule (burner program). The burner program is
indexed by limits of charge KWH, charge KWH/charge ton or stability factor, but
any metric can be used by reconfiguring the system. The system also logs EAF and
process state data every update interval (approximately 5 seconds).
1.2.1 Burners Description
The sidewall burner system consists of two Oxygen/Natural Gas burners, and
two Oxygen/Natural Gas burners with Carbon injection. The burners can be con-
trolled automatically from the electrode regulation system as noted above, from a
separate burner only control system, or manually by the operators. The flow rates of
each burner can be individually controlled. The primary mode of control is by the
electrode regulation system described above. The EAF’s sidewall burners can come
on at the same time as the electrical power, or at some later time during the melt-in
depending on how the burner control profile is configured. The burners normally
begin operating immediately after power is turned on and continue for approximately
60% of the melting cycle.
1.2.2 Oxygen Lance Description
The operators also have an Oxygen lance that is manually controlled. The
Oxygen lance is used to melt scrap that is piled in front of the EAF slag door, and
can also be used to deliver Oxygen to the liquid bath in the event of failure of one or
more of the sidewall burners. The Oxygen lance has no flow rate control.
8
1.3 Thesis Goals
The goals of this thesis are as follows:
1. Create a reasonably accurate model of the EAF melting process.
2. Develop a simulation utility that accepts a control profile similar to the actual
EAF control profile, and generates predictions of the process performance.
3. Develop an algorithm to search for an optimal control profile.
1.4 Definitions
• Burner Program - A sequence of flow rates for EAF sidewall burners and Carbon
injection.
• Charge - One bucket load of scrap.
• Charging - The process of placing scrap into the EAF.
• ChargeGas/Ton =ChargeGas
ChargeSize
• ChargeKWH/Ton =ChargeKWH
ChargeSize
• ChargeO2/Ton =ChargeO2
ChargeSize
• Charge Metric - An accumulated measurement that is reset to zero at the be-
ginning of each EAF charge. These metrics are typically normalized by the size
of the charge.
• Control Profile - In the context of this thesis, a control profile refers to the
collection of a burner program, power program and charge sizes.
9
• EAF - Electric Arc Furnace.
• Flat Bath - When the scrap is 100% melted, this is called a flat bath condition.
• Heat - One batch of steel.
• HeatGas/Ton =
∑3
n=1Charge[n]Gas
HeatSize
• HeatKWH/Ton =
∑3
n=1Charge[n]KWH
HeatSize
• HeatO2/Ton =
∑3
n=1Charge[n]O2
HeatSize
• Heat Metric - An accumulated measurement that is reset to zero at the begin-
ning of each heat. These metrics are typically normalized by the instantaneous
size of the heat (ie. the normalization factor is updated each charge).
• Heat Size =3∑
n=1
ChargeSize[n]
• I2H - Current squared times time.
• Power Program - A sequence of current set-points.
• Tapping - The process of transferring the liquid steel from the EAF into a ladle.
• T2T - Tap to tap time. The time required to produce one heat of steel.
• TPH - Tons per hour =HeatSize
T2T
10
CHAPTER 2
DATA
2.1 Data Logging
The electrode regulation system logs process data into binary data files each
update interval, which is approximately every 5 seconds. The system creates one
file per heat. The majority of the logged data is usable, but it does contain a small
fraction of outliers. A program was written using the Ruby scripting language (listings
A.1,2) to remove the outliers, filter heats with more than 3 charges, manipulate the
data into the desired regressor inputs and to create one large data file to simplify
data import to Matlab. Outliers were filtered according to the following constraints:
• Number of charges = 3
• 15.0 Tons < Charge 1 < 45.0 Tons
• 10.0 Tons < Charge 2 < 45.0 Tons
• 0.0 Tons < Charge 3 < 45.0 Tons
• T2T < 192 Minutes
After post-processing the data pool consisted of 786,584 observations spanning 856
heats. This is approximately 3 months worth of production data. Another program
(listing A.3) was written to calculate the necessary statistics of the filtered data to be
used for an objective function. Table 2.1 shows the basic statistics of the data pool.
11
Table 2.1: Data Statistics
Parameter Mean Std. Dev. Min Max
Heat Size(Tons) 88.21 2.83 75.4 93.6Charge 1(Tons) 38.77 2.4 28.2 43.3Charge 2(Tons) 30.31 2.35 23.4 36.9Charge 3(Tons) 19.13 3.26 7.0 31.0
Natural Gas (SCF) 18012 4477 3200 71600O2 (SCF) 55074 12630 6800 218600
Carbon (Lbs.) 645 228 0 1823KWH 33434 1806 19230 41350I2H 2163 133 1229 2740
T2T (Min.) 104 12.53 65.7 193.8Gas/Ton 204 - - -O2/Ton 624 - - -
Carbon/Ton 7.31 - - -KWH/Ton 379 - - -I2H/Ton 24.52 - - -
12
CHAPTER 3
SYSTEM IDENTIFICATION
There appears to be a lack of modeling in the steel industry. This is surprising
considering the maturity of the industry, the availability of software to aid with system
identification, and the abundance of wasted CPU cycles. The identification goal was
to construct a linear model with reasonable accuracy (< 25% error) that could be used
to simulate production of a heat given a control profile. An assumption was made
that with a proper choice of regressors, a linear model would be adequate within the
valid operating range of the process and equipment.
3.1 Melting Process Model Development
Creation of an EAF melting process model requires that the process times
and consumptions can be simulated with a reasonable amount of accuracy. This is
equivalent to being able to predict the completion of a melting cycle. If completion
of the melting cycle can be accurately modeled then the energy consumption can also
be predicted accurately.
The regressors chosen to develop the linear models to predict the required
parameters were chosen based on practical experience and correlation analysis. There
are several parameters that are required to be able to measure the EAF melting
process performance. The essential parameters are:
1. Tap to Tap Time
2. Power On Time
13
3. Accumulated KWH
4. Heat Size
5. Accumulated Oxygen
6. Accumulated Natural Gas
7. Accumulated Carbon
8. Electrode Consumption (I2H)
Some of the initial assumptions about the process proved to be false and are
noted below:
1. Charge number correlated very low with percent melted. The correlation coef-
ficient was -0.0007. This indicates that the number of the charge in the heat
does not have an impact on the instantaneous value of the completion of the
melting cycle, and is the reason there is only one set of model parameters for
the percent melted model instead of model parameters for each charge.
2. KWH consumption is strictly a function of power on time. During the initial
part of the melting cycle (bore-in) it was assumed that the KWH consumption
rate is less due to the instability of the arc. This is apparently not correct.
3. Using I2dt as a regressor for the I2H model resulted in high error, which leads
to the question of what the vendor is actually doing in the regulator to calculate
this metric.
14
I2H
Pon
I1
I2
I3
FIRModel
FIRModel
Offset
PonKWH
(Flow)(dt)
dt
Natural Gas Flow
O2 Flow
Carbon Flow
Natural Gas
O2
Carbon
FIRModel
Charge KWH/Ton
Heat KWH/Ton
Charge O2/Ton
Heat O2/Ton
Charge Gas/Ton
Heat Gas/Ton
Charge Carbon/Ton
Heat Carbon/Ton
Pon
Poff
Percent Melted
Figure 3.1: EAF Process Model
Matlab scripts were written to read the post-processed data and compute
parameters for a collection of FIR models to calculate the instantaneous percent
melted, I2H, and KWH consumption.
3.1.1 Electrical Energy Consumption
The EAF transformer is 33.6MVA and is always operated using the top tap
(500v secondary) and the electrode regulation control system in automatic. Given
the consistent electrical operating practice the EAF consumes a relatively consistent
amount of electrical energy vs. power on time. When the EAF is conducting current,
it typically consumes 416 KWH/Minute. Electrical energy consumption is metered
by the electrical utility provider and is calculated by the electrode regulation system.
15
Table 3.1: KWH Regressor Correlation Strengths
Regressor Correlation Strength
Power On Time 1.0
The KWH consumption model is of the form:
KWH[n] = θ1 + θ2Pon[n − 1] (3.1)
3.1.2 Oxygen, Gas and Carbon Consumption
Oxygen and Natural Gas consumption are not metered, but are calculated in
the control system based on flow rates, time and temperature compensation. Carbon
consumption is metered based on the change in weight of a scale over time. The
consumption model for each of these is of the form:
N∑
n=1
(FlowRate[n − 1])(dt) (3.2)
3.1.3 Electrode Consumption
Graphite electrode is consumed via oxidation and tip sublimation. Each mode
accounts for half of the total consumption. The following equations can be used to
calculate each mode of consumption:
Ctip = RsubI2tpon(Lbs.) (3.3)
16
Cox = RoxAtt2t/P (Lbs.) (3.4)
where
Rsub = Average Sublimation Rate
Rox = Average Oxidation Rate
I = Phase Current
tpon = Power On Time
tt2t = Tap To Tap Time
A = Electrode Surface Area
P = Heat Size (Tons/Heat)
Using the output of the I2H model, the electrode consumption is simple to
calculate based on the above equations.
The regressors for the I2H model are power on time and each of the phase
currents. The model has the following form:
I2H(n) = θ1Pon(n − 1) + θ2Ia(n − 1) + θ3Ib(n − 1) + θ3Ic(n − 1) (3.5)
3.1.4 Percent Melted
The percent melted model is the most critical in terms of being able to provide
an accurate simulation, the reason being that the consumptions are primarily time
based, and the percent melted model determines the length of time that the EAF
is in the ’on’ state. Unfortunately, there is no deterministic indication of when the
17
Table 3.2: I2H Regressor Correlation Strengths
Regressors Correlation Strength
Power On Time 1.0Phase 1 Current 0.34Phase 2 Current 0.35Phase 3 Current 0.34
melting of a charge is complete. The operators use a combination of factors to make
a determination of when to turn off the power and put the next charge into the EAF.
Senior operators are surprisingly consistent at making this determination, considering
the variance of the environment. Junior operators sometimes have problems making
this determination. Factors that contribute to the determination are charge size,
charge KWH consumed, scrap conditions and sound (stability level).
The percent melted model is a per charge model that uses charge metrics, heat
metrics and times for regressors. The model provides an output value ranging from 0
to 100 which represents the completion of the melting cycle for each charge in a heat.
In order to construct a method for calculating the percent melted model pa-
rameters, the data files were modified to include a linearly spaced percent melted
metric ranging from 0 to 100 and mapped from the beginning to the end of each
charge. The model has the following form:
18
Table 3.3: Percent Melted Regressor Correlation Strengths
Regressor Correlation Strength
Charge Number -0.0007Charge KWH/Ton 0.66Heat KWH/Ton 0.59Charge O2/Ton 0.66Heat O2/Ton 0.45
Charge Gas/Ton 0.71Heat Gas/Ton 0.45
Charge Carbon/Ton 0.44Heat Carbon/Ton 0.38Power On Time 0.35Power Off Time 0.07
PM(n) = θ1
ChargeKWH(n − 1)
ChargeSize+ θ2
HeatKWH(n − 1)
HeatSize+
θ3
ChargeO2(n − 1)
ChargeSize+ θ4
HeatO2(n − 1)
HeatSize+
θ5
ChargeGas(n − 1)
ChargeSize+ θ6
HeatGas(n − 1)
HeatSize+
θ7
ChargeCarbon(n − 1)
ChargeSize+ θ8
HeatCarbon(n − 1)
HeatSize+
θ9PonTime(n − 1) + θ10PoffTime(n − 1)
(3.6)
Charge metrics (ie. ChargeKwh/Ton, ChargeGas/Ton) are reset to 0 at the
end of the melting cycle for each charge. Heat metrics (ie. HeatKwh/Ton, HeatGas/-
Ton) accumulate throughout the heat and exhibit non-linear transients at the charge
points. However, these non-linearities do not impact the model because the dynamics
of the non-linearity can be exactly replicated in the regressors.
The last charge of the heat is characterized by a refine period which accounts
19
for the higher electrical energy consumed by that charge. The heat metrics provide
a stable form of feedback into the model. The following figures (3.2 through 3.12)
display typical characteristics of the regressors and span approximately two heats
chosen randomly from the data.
20
0 500 1000 1500 2000 2500 30000
100
200
300
400
500
600
700
800
900
Figure 3.2: Charge KWH/Ton
21
0 500 1000 1500 2000 2500 30000
50
100
150
200
250
300
350
400
Figure 3.3: Heat KWH/Ton
22
0 500 1000 1500 2000 2500 30000
50
100
150
200
250
300
Figure 3.4: Charge Gas/Ton
23
0 500 1000 1500 2000 2500 30000
50
100
150
200
250
300
Figure 3.5: Heat Gas/Ton
24
0 500 1000 1500 2000 2500 30000
100
200
300
400
500
600
700
800
900
1000
Figure 3.6: Charge O2/Ton
25
0 500 1000 1500 2000 2500 30000
100
200
300
400
500
600
700
Figure 3.7: Heat O2/Ton
26
0 500 1000 1500 2000 2500 30000
5
10
15
20
25
30
35
40
Figure 3.8: Charge Carbon/Ton
27
0 500 1000 1500 2000 2500 30000
1
2
3
4
5
6
7
8
9
Figure 3.9: Heat Carbon/Ton
28
0 500 1000 1500 2000 2500 30000
10
20
30
40
50
60
70
80
Figure 3.10: Power On Time
29
0 500 1000 1500 2000 2500 30006
8
10
12
14
16
18
20
22
24
26
Figure 3.11: Power Off Time
30
0 500 1000 1500 2000 2500 30000
500
1000
1500
2000
2500
Figure 3.12: I2h
31
3.2 Model Accuracy
Model accuracies were measured by testing the models against the data set
and on new data.
From [2], the multiple correlation coefficient (squared) is defined as:
R2
y =
∑Nt=1
y2
N(t)∑N
t=1y2(t)
= 1 −
∑Nt=1
ǫ2
N(t)∑N
t=1y2(t)
(3.7)
where y is the model’s predicted output and ǫ is the residual error between actual
output and predicted output. R2
y measures the proportion of the total variation of
y that is explained by the regression. In the table below RMSE represents the root
mean squared error.
Table 3.4: Model Performance
Model RMSE R2
y
Percent Melted 12.24% 95.53%KWH 584 KWH 99.9%I2H 45.8 KAmp2 Hours 99.85%O2 5662 SCF 97.9%Gas 1861 SCF 98.2%
Carbon 60 Lbs. 96.1%
To put the numbers from table 3.4 into context, 584 KWH is approximately
1 minute of EAF operating time, 45.8 I2H is 2% of the mean total, 5662 SCF O2 is
10% of the mean total, 1861 SCF Gas is 10% of the mean total and 60 lbs. Carbon
32
is 9.3% of the mean total.
Figures 3.13 through 3.18 detail the spread of the residuals for the models
when tested against the data set.
−100 −80 −60 −40 −20 0 20 40 60 80 1000
2
4
6
8
10
12x 10
4
Percent Melted Error
Fre
quen
cy
Figure 3.13: Percent Melted Residuals Histogram
33
−8000 −6000 −4000 −2000 0 2000 40000
0.5
1
1.5
2
2.5
3
3.5
4x 10
4
KWH Error
Fre
quen
cy
Figure 3.14: KWH Residuals Histogram
34
−600 −500 −400 −300 −200 −100 0 100 200 3000
0.5
1
1.5
2
2.5
3x 10
4
Fre
quen
cy
I2H Error
Figure 3.15: I2H Residuals Histogram
35
−2000 −1500 −1000 −500 0 500 1000 1500 20000
0.5
1
1.5
2
2.5
3
3.5
4
4.5
5x 10
4
Fre
quen
cy
O2 Error (SCF)
Figure 3.16: O2 Residuals Histogram
36
−1000 −800 −600 −400 −200 0 200 400 600 800 10000
0.5
1
1.5
2
2.5
3
3.5
4
4.5x 10
4
Gas Error (SCF)
Fre
quen
cy
Figure 3.17: Gas Residuals Histogram
37
−200 −150 −100 −50 0 50 100 150 2000
1000
2000
3000
4000
5000
6000
Fre
quen
cy
Carbon Error (Lbs.)
Figure 3.18: Carbon Residuals Histogram
38
Following the advice of [2] for model validation, the autocorrelation of the
percent melted model residuals was computed to show that the error is uncorrelated
from one heat to the next. The maximum tap to tap time in the data set is 194
minutes. The observation update rate is approximately 5 seconds. Taking the au-
tocorrelation of the percent melted residuals with a lag of 30000 samples gives the
correlation of the error with respect to approximately 10 heats, and is shown in fig-
ure 3.19. As expected there is no significant correlation between residuals that are
’far away’. However, looking at the residuals of heats that are ’close by’, there is
some minor amount of correlation as shown in figure 3.20. The exact source of the
correlation could not be determined, but there are a few obvious possibilities. The
operators leave a small amount of liquid steel in the furnace after tap. This is called a
hot heel and is typically about 2.5 Tons. The hot heel reduces the amount of energy
required to melt the first charge by acting as a thermal energy source to pre-heat the
first charge. The EAF must be completely drained (ie. no hot heel) periodically (≃40
heats) for inspection of the bottom. The EAF performance for the following heat will
exhibit different performance characteristics. Another possible explanation has to do
with the scrap. Scrap metal is brought into the melt shop in rail cars as previously
noted. The quality of scrap has an impact on the performance of the process. An
average load of scrap can supply approximately 10 heats. The quality of scrap can
vary widely from one load to the next. It is possible that the residuals have a higher
degree of correlation within the same load of scrap.
39
−3 −2 −1 0 1 2 3
x 104
−40
−20
0
20
40
60
80
100
120
140
160
Lag
Figure 3.19: Percent Melted Residuals Autocorrelation
40
−8000 −6000 −4000 −2000 0 2000 4000 6000 8000−40
−20
0
20
40
60
80
100
120
140
160
Lag
Figure 3.20: Percent Melted Residuals Autocorrelation
41
CHAPTER 4
SIMULATION
A Ruby program was written to simulate the production of a heat using the
EAF model (listing A.7). The program reads a control profile (listing A.10) from a
text file, which has a format very similar to the actual control profile. The profile
contains a power program, a burner program, charge sizes and power off times. The
simulator is a useful tool due to the fact that changes can be made to the profile,
and the simulator will provide an immediate indication of how the change will effect
the process. The simulator writes a text report of the heat to the standard output
(listing A.13).
42
CHAPTER 5
OPTIMIZATION
The power program, burner program and charges for a 3 charge heat contain
873 parameters. A genetic algorithm was implemented to attempt optimization of the
control profiles and charge sizes for the EAF process in terms of cost and production.
5.1 Optimization Goal
The optimization goal was to use the genetic algorithm to search for good
control profiles and to gauge the current quality of process control.
5.2 Objective Function
An objective function was constructed to guide the genetic algorithm during
optimization:
f(θ, TPH,Cost) =1
θ TPHTPH
+ (1 − θ)Cost/Ton
Cost/Ton
, 0 ≤ θ ≤ 1 (5.1)
TPH is the mean tons per hour and Cost/Ton is the mean cost per ton where cost
is the sum of the costs of scrap, electrode, electrical energy, Oxygen, Natural Gas
and Carbon. θ is used to steer the algorithm toward optimization of production or
optimization of cost/ton. When θ = 0.5, and all of the performance metrics are equal
to the mean values, the fitness function produces a value of 1. When the performance
of the control profiles produces something less than the mean, the fitness function
provides a value less than one. If the control profiles produce performance above the
mean, the fitness function produces a value greater than one.
43
5.3 Genetic Algorithm
A genetic algorithm (GA) was coded using the Ruby scripting language. The
genetic algorithm creates and maintains a pool of random control profiles within some
very loose constraints. It is not logical to impose a great deal of constraints on the
GA due to its ability to evade them via mutation and recombination. The outline of
the algorithm is as follows:
1. Create an initial pool of individuals (control profiles). Individuals are repre-
sented programmatically as an encoded binary string.
2. Simulate the production of a heat using each individual in the pool.
3. Evaluate the fitness of each individual using the the performance metrics com-
puted during the heat simulation. Save the individual with the highest fitness
into a file.
4. Perform selection using the tournament selection algorithm defined in [3].
5. Perform crossover using the selected individuals to obtain offspring for the next
generation.
6. Perform mutation on the offspring.
7. Go to step 2 and repeat the algorithm for some variable number of generations.
5.3.1 Initial Pool
The GA is started by creating a random pool of individuals. An individual is
composed of the following:
44
• 3 randomly sized charges constrained to sum to between a minimum and max-
imum heat size.
• A burner program with random flow rates and limits. The flow rates are con-
strained between a minimum and maximum.
• A power program with random current set-points and limits. The current set-
points are constrained between a minimum and maximum.
There are no definitive guidelines in the literature for the initial pool size. [1] states
that the initial pool size will need to be varied per application to achieve the best
results. The pool size was varied between 25 and 200. An initial pool size of 70 was
used for the majority of the simulations. An initial pool size between 50-75 seemed
to provide a balance between adequate diversity in the population and reasonable
simulation times.
5.3.2 Simulation
Each individual (control profile) is used to simulate the production of a heat
using the simulator previously described. The fitness of the individual is calculated
using the results of the simulation. The control profile is saved to a file if the individual
has the highest fitness value.
5.3.3 Selection
Stochastic sampling with replacement or weighted roulette wheel sampling is
probably the most popular selection algorithm used in GAs. The problem with the
algorithm is that it often leads to situations where a few individuals dominate the
45
pool, decreasing the diversity of the population. The tournament selection algorithm
outlined in [3] is a simple alternative to the weighted roulette wheel algorithm. An
outline of the algorithm is as follows:
1. Randomly choose 2 individuals.
2. Choose a random number r between 0 and 1.
3. If r is less than some constant (0.75) choose the individual with the highest
fitness, otherwise choose the individual with the lower fitness value.
This process is repeated until the required number of individuals have been selected
for crossover.
5.3.4 Crossover
Crossover is performed by selecting 2 individuals from the pool, randomly
selecting a crossover point and interchanging the parts from each individual before
and after the crossover points. Each crossover operation generates 1 offspring.
5.3.5 Mutation
Mutation is performed by flipping a bit in the binary encoded string. Like other
parameters that control the GA, there is no definitive best value for mutation rate
described in the literature. 1 mutation per 1000 bits is a popular mutation rate. Due
to the longer string lengths of this implementation (≃ 8000 bits), a lower mutation
rate of 1 mutation per 10000 bits was used for the majority of the simulations.
46
5.3.6 GA Simulations
The genetic algorithm generally functioned as expected with a few notable
exceptions. The initial fitness of the population is always low, but generally increases
rapidly with each generation to some asymptote (see figure 5.2).
The GA learns that it can significantly increase performance by decreasing
the heat size and significantly increasing the flow rates for Natural Gas, Carbon and
Oxygen. This behavior is problematic and points out opportunities for improvement
in the model and in the GA.
The contribution of the sidewall burners to the melting process is not con-
tinuous. As the scrap in the EAF melts, the volume of the scrap decreases and the
contribution of the sidewall burners to the melting process decreases due to the re-
duced surface area of the scrap. As a general rule of thumb, when the scrap is 60%
melted, the sidewall burners no longer contribute to the melting process.
When the charge has reached a flat bath condition, high flow rates from the
sidewall burners can not be used. If high flow rates are used, liquid steel can be
splashed into the area between the EAF roof and sidewalls, resulting in the roof
being welded to the top of the EAF shell.
The EAF model does not account for metallurgical effects of the process.
Process metallurgical data is collected, but was not in a useable format at the time
of model creation. Carbon is used as an energy source in the melting process. Each
grade of steel has limitations on the concentration of Carbon. Large amounts of
Carbon can increase melting performance, but will result in long operational delays
47
at the end of the heat to decarburize the liquid steel.
There are many constraints on the EAF melting process. It would not be
feasible nor logical to encode all of the constraints into the GA due to its ability to
evade them through mutation and recombination. Additionally, heavily constraining
a global search algorithm defeats the purpose of its use.
In order to rectify these issues, the objective function was modified to include
penalties as outlined in [1] for overuse of O2, Natural Gas and Carbon. If the O2,
Natural Gas or Carbon consumption exceeds the mean values plus 1 standard devia-
tion, a penalty of a proportion of the square of the difference is imposed on the fitness
function.
Gas Penalty := αg(gas − (gas + σg))2
Carbon Penalty := αc(carbon − (carbon + σc))2
O2 Penalty := αo2(O2 − (O2 + σo2
))2
The EAF sidewall burners are typically operated in a synchronous manner. In
general, the GA does not produce burner profiles that are synchronous. The most
obvious trend in the GA simulations is that the GA consistently guides the control
profile toward smaller heats, regardless of how the tuning parameters were set (TPH
or Cost). This behavior lead us to attempt smaller heats (see table 6.2).
48
0 20 40 60 80−2.5
−2
−1.5
−1
−0.5
0x 10
4
Mutation Probability = 0.0001
Ave
rage
Fitn
ess
0 50 100−20000
−15000
−10000
−5000
0
5000
Mutation Probability = 0.00015
Ave
rage
Fitn
ess
0 20 40 60 80−2.5
−2
−1.5
−1
−0.5
0
0.5x 10
4
Mutation Probability = 0.001
Ave
rage
Fitn
ess
0 20 40 60 80−2.5
−2
−1.5
−1
−0.5
0x 10
4
Mutation Probability = 0.01
Ave
rage
Fitn
ess
Figure 5.1: Fitness Vs. Mutation Rate, θ = 0.5
49
0 20 40 60 80−4
−3
−2
−1
0
1
2x 10
4
Generation
Ave
rage
Fitn
ess
0 20 40 60 8040
50
60
70
80
90
100
Generation
Ave
rage
Hea
t Siz
e (C
harg
ed T
ons)
0 20 40 60 80200
220
240
260
280
300
Generation
Ave
rage
KW
H/T
on
0 20 40 60 80200
300
400
500
600
700
800
Generation
Ave
rage
O2/
Ton
0 20 40 60 80200
250
300
350
400
450
500
Generation
Ave
rage
Gas
/Ton
0 20 40 60 8010
15
20
25
30
Generation
Ave
rage
Car
bon/
Ton
Figure 5.2: GA Performance (Balanced), θ = 0.5
50
0 20 40 60 8040
50
60
70
80
90
100
Generation
Ave
rage
Ton
s/H
our
0 20 40 60 8040
50
60
70
80
90
100
Generation
Ave
rage
Hea
t Siz
e (C
harg
ed T
ons)
0 20 40 60 80100
150
200
250
300
350
400
Generation
Ave
rage
KW
H/T
on
0 20 40 60 80400
500
600
700
800
Generation
Ave
rage
O2/
Ton
0 20 40 60 80200
250
300
350
400
450
500
Generation
Ave
rage
Gas
/Ton
0 20 40 60 800
10
20
30
40
Generation
Ave
rage
Car
bon/
Ton
Figure 5.3: GA Performance, θ = 1.0 (TPH)
51
0 20 40 60 80210
212
214
216
218
Generation
Ave
rage
Cos
t/Ton
0 20 40 60 8055
60
65
70
75
80
85
90
Generation
Ave
rage
Hea
t Siz
e (C
harg
ed T
ons)
0 20 40 60 80180
200
220
240
260
280
300
320
Generation
Ave
rage
KW
H/T
on
0 20 40 60 80300
400
500
600
700
800
Generation
Ave
rage
O2/
Ton
0 20 40 60 80200
250
300
350
400
450
Generation
Ave
rage
Gas
/Ton
0 20 40 60 805
10
15
20
25
30
35
Generation
Ave
rage
Car
bon/
Ton
Figure 5.4: GA Performance, θ = 0.0 (Cost/Ton)
52
CHAPTER 6
RESULTS
6.1 Model Performance Using New Data
On March 3 2006 a new EAF control profile was put in place as part of ongoing
efforts to reduce energy consumption. In addition to the new profile, operators were
encouraged to charge only the amount of scrap necessary and also to reduce the
amount of Carbon being used. The new control profile and operating guidelines
had the effect of shifting the mean values of the process. Table 6.1 details the model
accuracy that results from using the same control profile and comparing the simulator
outputs with the process data for the first 3 weeks of March.
The GA consistently steered the profile toward smaller heat sizes. On March
24 trials were conducted to determine the accuracy of the model on smaller heat sizes
and to validate the GA predictions. The target heat size was 75 tons. The actual
heat size was an average of 77.76 tons. The model accuracy is detailed in table 6.2.
The high error on the Carbon consumption is the result of a control system anomaly.
Producing smaller heats proved to be difficult operationally. The geometry of
Table 6.1: Model Percent Error on New Data
Output 3/3-3/10 3/10-3/17 3/17-3/24Pon 2.56% 0.39% -7.9%
KWH/Ton 2.2% -0.25% -5.06%Gas/Ton -3.77% -9.41% -14.2%O2/Ton -8.55% -18.8% -11.0%
Carbon/Ton -7.94% 19.66% -3.6%
53
Table 6.2: Model Percent Error on Smaller Heats
Output Percent ErrorPon -0.85%
KWH/Ton -2.55%Gas/Ton -2.65%O2/Ton -1.51%
Carbon/Ton 26.04%
the EAF shell was designed for approximately 80 Tons. As the heat size decreases, the
level of the liquid steel in the EAF decreases, making it difficult to take temperature
and chemical samples of the liquid steel.
The simulation of our current control profile generates a fitness level of ap-
proximately 1.0 with the TPH/Cost balance set at 0.5. In general, with the penalty
functions imposed on the GA it was not able to find a control profile at or above this
level of performance within the constraints of the process. This leads to the belief
that the GA is better at finding local minima than it is at finding global minima when
using it as a global search tool for this application. Additionally, this should be con-
sidered indicative of the high degree of expertise that the operators have developed
in EAF process control.
6.2 Conclusion and Opportunities
The goals of this thesis have been met. The linear model is reasonably accurate
within the operating constraints of the process. The simulator can be used with
the model to investigate what effect changes to the control profile will have on the
54
process. The genetic algorithm has provided an indication that smaller heat sizes
may be beneficial in terms of production capacity and cost, which is in agreement
with the opinions of several experts in the field. The following are areas where the
model and genetic algorithm could be improved upon given the time.
6.2.1 Metallurgical Effects
Future versions of the model should take into account the metallurgical effects
of Carbon addition. In particular, large amounts of injection Carbon will lead to
operational delays as previously noted. The operational delay should be accounted
for in the tap to tap time of the simulation and the additional Oxygen required to
decarburize the heat. If metallurgical data can be put into a useable format, another
control could be added to account for these issues.
6.2.2 Scrap and Layering
A charge in the current model was generalized to be composed of one scrap
type and an average cost. Future versions of the model should take into account the
effects of the various scrap types, layering of scrap in the bucket, densities and costs.
6.2.3 Flux Additions
Fluxing agents were not included in the model due to a lack of data in a useable
format. Future versions of the model should take into account the contribution of
fluxing agents and Carbon that is put into the EAF via the charge bucket.
55
6.2.4 Burner Non-Linearities
The EAF sidewall burners contribute to the melting process for approximately
60% of the melting cycle as previously mentioned. Future versions of the model should
take this non-linear behavior into account via the model instead of using penalties on
the objective function.
6.2.5 Genetic Algorithm
The genetic algorithm is extremely tunable via the mutation probability, crossover
probability, ratio of parents to offspring and the various selection and crossover meth-
ods. In [4] a method is described where the genetic algorithm’s tuning parameters
are encoded into the string and optimized along with the system, the rational being
that populations who are better able to adapt are more fit. It would be interesting
see the optimization results given this encoding of the genetic algorithm.
56
APPENDIX A
SOURCE CODE
A.1 Source Listing: filter rph data.rb
# This script filters the Smart Arc RPH csv# files and the data. This is the new script# that filters outliers and separates# data into charges.
# Define some constantsRPH DIR = "N:/meltshop/share/logs/Smart Arc CSV"
C1 FILE = "P:/projects/thesis/data/c1 eaf data"
C2 FILE = "P:/projects/thesis/data/c2 eaf data"
C3 FILE = "P:/projects/thesis/data/c3 eaf data"
C4 FILE = "P:/projects/thesis/data/c4 eaf data"
# Column IndexesTIME STAMP = 0HEAT MWH = 1PON = 2POFF = 3I1 = 4I2 = 5I3 = 6SF1 = 7SF2 = 8SF3 = 9I2H1 = 10I2H2 = 11I2H3 = 12CHARGE NUM = 13LANCE O2 FLOW = 14I1SP = 15I2SP = 16I3SP = 17B1 GAS FLOW = 18CJ1 GAS FLOW = 19B2 GAS FLOW = 20CJ2 GAS FLOW = 21B1 O2 FLOW = 22CJ1 O2 FLOW = 23
57
B2 O2 FLOW = 24CJ2 O2 FLOW = 25CJ1 C FLOW = 26CJ2 C FLOW = 27TOTAL GAS = 28TOTAL O2 = 29TOTAL C = 30C1 WEIGHT = 31C2 WEIGHT = 32C3 WEIGHT = 33C4 WEIGHT = 34
rph csv files = Dir.entries(RPH DIR) [".", ".."]# Insert path for each filerph csv files.collect! {|f| RPH DIR + "/" + f}
# Open the destination filesc1 outfile = File.open(C1 FILE, "w")c2 outfile = File.open(C2 FILE, "w")c3 outfile = File.open(C3 FILE, "w")c4 outfile = File.open(C4 FILE, "w")
# Inputs 18# I1sp, I2sp, I3sp,# cj1o2flow, cj1gasflow, cj1cflow,# cj2o2flow, cj2gasflow, cj2cflow,# b1o2flow, b1gasflow,# b2o2flow, b2gasflow,# lanceo2flow,# charge weight,# pon, poff
# Ouputs 11# %melted,# avg sf, avg i2h, kwh# o2, gas, c,# avg I
def format charge(filename, charge)
fcharge = [] # Formatted Charge Data
delta = 100.0 / charge.length()percent = 0.0
58
charge.each index {|i|
inputs = []outputs = []
# Inputsinputs << charge[i][I1SP].to f ∗ 1000inputs << charge[i][I2SP].to f ∗ 1000inputs << charge[i][I3SP].to f ∗ 1000inputs << charge[i][CJ1 O2 FLOW]inputs << charge[i][CJ1 GAS FLOW]inputs << charge[i][CJ1 C FLOW]inputs << charge[i][CJ2 O2 FLOW]inputs << charge[i][CJ2 GAS FLOW]inputs << charge[i][CJ2 C FLOW]inputs << charge[i][B1 O2 FLOW]inputs << charge[i][B1 GAS FLOW]inputs << charge[i][B2 O2 FLOW]inputs << charge[i][B2 GAS FLOW]
lance o2 flow = charge[i][LANCE O2 FLOW].to ilance o2 flow = 0 if lance o2 flow < 0inputs << lance o2 flow
cnum = charge[0][CHARGE NUM].to i
if cnum == 1inputs << charge[0][C1 WEIGHT]
elsif cnum == 2inputs << charge[0][C2 WEIGHT]
elsif cnum == 3inputs << charge[0][C3 WEIGHT]
elsif cnum == 4inputs << charge[0][C4 WEIGHT]
end
if fcharge.length == 0inputs << charge[i][PON].to f
else
if charge[i 1][PON].to f == charge[i][PON].to finputs << charge[i][PON].to f + 0.05
elsif charge[i 1][PON].to f > charge[i][PON].to finputs << charge[i][PON].to f + 0.025
59
else
inputs << charge[i][PON]end
end
inputs << charge[i][POFF]
# Outputspercent += deltaoutputs << percentoutputs << (charge[i][SF1].to f +
charge[i][SF2].to f + charge[i][SF3].to f)/3.0outputs << (charge[i][I2H1].to f +
charge[i][I2H2].to f + charge[i][I2H3].to f)/3.0outputs << charge[i][HEAT MWH].to f ∗ 1000outputs << charge[i][TOTAL O2].to f ∗ 100
# Get rid of messed up heats with crazy O2if outputs[ 1] > 100000
puts filename + " Has o2 > 100000!!!"
exit(1)end
outputs << charge[i][TOTAL GAS].to f ∗ 100outputs << charge[i][TOTAL C]outputs << ((charge[i][I1].to f ∗ 10) + (charge[i][I2].to f ∗ 10) +
(charge[i][I3].to f ∗ 10))/3.0
unless (inputs.length() + outputs.length()) == 25puts "From: " + filename + " Length != 25"
puts "Inputs: "
p inputsputs "Outputs: "
p outputsexit 1
end
unless inputs.index(nil) == nilputs "Input has nil"
exit 1end
unless outputs.index(nil) == nilputs "Output has nil"
exit 1end
60
fcharge << (inputs + outputs).join(" ")}
return fchargeend
at exit {# Close the data filesc1 outfile.close()c2 outfile.close()c3 outfile.close()c4 outfile.close()
}
rph csv files.each {|f|
rph file = File.open(f, "r")rph file.gets() # Get rid of banner
charge1 = []charge2 = []charge3 = []charge4 = []
rph file.each {|row|data = row.split(’,’)charge num = data[CHARGE NUM].to iif charge num == 1
charge1 << data unless data[C1 WEIGHT].to f < 0elsif charge num == 2
charge2 << data unless data[C2 WEIGHT].to f < 0elsif charge num == 3
charge3 << data unless data[C3 WEIGHT].to f < 0elsif charge num == 4
charge4 << data unless data[C4 WEIGHT].to f < 0end
}
charge1 = format charge(f, charge1) unless charge1 == []charge2 = format charge(f, charge2) unless charge2 == []charge3 = format charge(f, charge3) unless charge3 == []charge4 = format charge(f, charge4) unless charge4 == []
61
four charge heat = true unless charge4 == []unless four charge heat
charge1.each {|row| c1 outfile.puts(row)}charge2.each {|row| c2 outfile.puts(row)}charge3.each {|row| c3 outfile.puts(row)}
end
charge4.each {|row| c4 outfile.puts(row)}
}
62
A.2 Source Listing: build eaf data.rb
# This script filters the Smart Arc RPH csv# files
# Define some constantsRPH DIR = "../data/smart arc csv"
DATA FILE = "../data/eaf data"
# Column IndexesTIME STAMP = 0HEAT MWH = 1PON = 2POFF = 3I1 = 4I2 = 5I3 = 6SF1 = 7SF2 = 8SF3 = 9I2H1 = 10I2H2 = 11I2H3 = 12CHARGE NUM = 13LANCE O2 FLOW = 14I1SP = 15I2SP = 16I3SP = 17B1 GAS FLOW = 18CJ1 GAS FLOW = 19B2 GAS FLOW = 20CJ2 GAS FLOW = 21B1 O2 FLOW = 22CJ1 O2 FLOW = 23B2 O2 FLOW = 24CJ2 O2 FLOW = 25CJ1 C FLOW = 26CJ2 C FLOW = 27TOTAL GAS = 28TOTAL O2 = 29TOTAL C = 30C1 WEIGHT = 31C2 WEIGHT = 32C3 WEIGHT = 33
63
C4 WEIGHT = 34
rph csv files = Dir.entries(RPH DIR) [".", ".."]# Insert path for each filerph csv files.collect! {|f| RPH DIR + "/" + f}
# Open the destination filesdata file = File.open(DATA FILE, "w")
# Format the charge into rowsdef format charge(filename, charge, kwh base, o2 base, gas base, carbon base)
fcharge = [] # Formatted Charge Data
charge weight = 0.0heat size = 0.0
cnum = charge[0][CHARGE NUM].to iif cnum == 1
charge weight = charge[ 1][C1 WEIGHT].to fheat size = charge weight
elsif cnum == 2charge weight = charge[ 1][C2 WEIGHT].to fheat size = charge[ 1][C1 WEIGHT].to f + charge weight
elsif cnum == 3charge weight = charge[ 1][C3 WEIGHT].to fheat size = charge[ 1][C1 WEIGHT].to f + charge[ 1][C2 WEIGHT].to f + charge weight
end
delta = 100.0/charge.length()pm = 0.0dt = 0.0kwhz = 0.0pmz = 0.0i2hz = 0.0kwh = 0.0o2 = 0.0gas = 0.0carbon = 0.0
charge.each index {|i|
if i >= 1dt = charge[i][PON].to f charge[i 1][PON].to f
64
dt = 0.05 if dt == 0.0end
kwh = charge[i][HEAT MWH].to f∗1000.0o2 = charge[i][TOTAL O2].to f∗100.0gas = charge[i][TOTAL GAS].to f∗100.0carbon = charge[i][TOTAL C].to fcj1 o2 flow = charge[i][CJ1 O2 FLOW]cj1 gas flow = charge[i][CJ1 GAS FLOW]cj1 c flow = charge[i][CJ1 C FLOW]cj2 o2 flow = charge[i][CJ2 O2 FLOW]cj2 gas flow = charge[i][CJ2 GAS FLOW]cj2 c flow = charge[i][CJ2 C FLOW]b1 o2 flow = charge[i][B1 O2 FLOW]b1 gas flow = charge[i][B1 GAS FLOW]b2 o2 flow = charge[i][B2 O2 FLOW]b2 gas flow = charge[i][B2 GAS FLOW]chrgkwhpt = (kwh kwh base)/charge weightheatkwhpt = kwh/heat sizechrgo2pt = (o2 o2 base)/charge weightheato2pt = o2/heat sizechrggaspt = (gas gas base)/charge weightheatgaspt = gas/heat sizechrgcpt = (carbon carbon base)/charge weightheatcpt = carbon/heat size
atten factor = 1 (1.0/80.0)∗pmzatten factor = 0.0 if pmz > 80.0atten chrggaspt = atten factor ∗ chrggasptatten heatgaspt = atten factor ∗ heatgaspt
pm += deltai2h = (charge[i][I2H1].to f +
charge[i][I2H2].to f + charge[i][I2H3].to f)/3.0i1 = charge[i][I1].to f∗10.0i2 = charge[i][I2].to f∗10.0i3 = charge[i][I3].to f∗10.0
i1dt = i1∗dti2dt = i2∗dti3dt = i3∗dt
i12dt = i1∗i1∗dt/60.0i22dt = i2∗i2∗dt/60.0
65
i32dt = i3∗i3∗dt/60.0
pon = charge[i][PON]poff = charge[i][POFF]
if chrggaspt < 0 or chrggaspt > 200000puts filename+" is messed up."
exit(1)end
ar = [kwh, kwhz, i1dt, i2dt, i3dt,pm, pmz, chrgkwhpt, heatkwhpt, chrgo2pt,heato2pt, chrggaspt, heatgaspt, chrgcpt,heatcpt, i2h, i2hz, i12dt, i22dt, i32dt,pon, poff, o2, gas, carbon,cj1 o2 flow, cj1 gas flow, cj1 c flow,cj2 o2 flow, cj2 gas flow, cj2 c flow,b1 o2 flow, b1 gas flow,b2 o2 flow, b2 gas flow,cnum, i1, i2, i3, atten chrggaspt, atten heatgaspt]
unless ar.length() == 41puts "row length = " + ar.length().to sputs "Parsing file " + filenamep arexit
end
str = ar.join(" ")fcharge << ar.join(" ")
pmz = pmi2hz = i2hkwhz = kwh
}
return([fcharge, kwh, o2, gas, carbon])end
at exit {# Close the data filesdata file.close()
}
66
###################################################################rph csv files.each {|f|
rph file = File.open(f, "r")rph file.gets() # Get rid of banner
charge1 = []charge2 = []charge3 = []charge4 = []
c1 weight = 0.0c2 weight = 0.0c3 weight = 0.0c4 weight = 0.0t2t = 0.0
data = []rph file.each {|row|
row.chomprow.tr!("\r\n", ’’)data = row.split(’,’)charge num = data[CHARGE NUM].to i
if charge num == 1charge1 << data
elsif charge num == 2charge2 << data
elsif charge num == 3charge3 << data
elsif charge num == 4charge4 << data
end
}# Data will contain the values of the last# row of data for the heat.t2t = data[PON].to f + data[POFF].to fc1 weight = data[C1 WEIGHT].to fc2 weight = data[C2 WEIGHT].to fc3 weight = data[C3 WEIGHT].to fc4 weight = data[C4 WEIGHT].to f
# ONLY TAKE THREE CHARGE HEATS FOR NOW
67
# Filtering T2T time for mean value + 1 sigma# The cx != [] predicates are here because there# was at least one case where they swung the roof# and incremented the charge. This resulted in# c3 weight being ok, but the charge number was# 4, and therefore all of the charge3 data went in# to charge4. Just throw these away.if ((charge4 == []) and
(c1 weight > 15.0) and (c1 weight < 45.0) and
(c2 weight > 10.0) and (c2 weight < 45.0) and
(c3 weight > 0.0) and (c3 weight < 45.0) and
(t2t < 192) and (charge1 != []) and (charge2 != []) and
(charge3 != []))
charge1, kwh base, o2 base, gas base, c base =format charge(f, charge1, 0.0, 0.0, 0.0, 0.0)
#puts ”base1 = ”+kwh base.to s+” ”+o2 base.to s+” ”+gas base.to s+” ”+c base.to scharge2, kwh base, o2 base, gas base, c base =
format charge(f,charge2,kwh base,o2 base,gas base,c base)
#puts ”base2 = ”+kwh base.to s+” ”+o2 base.to s+” ”+gas base.to s+” ”+c base.to scharge3, kwh base, o2 base, gas base, c base =
format charge(f,charge3,kwh base,o2 base,gas base,c base)
total = charge1 + charge2 + charge3
total.each {|row|data file.puts(row)
}else
puts "File "+f+" has bad data: c1="+c1 weight.to s+" c2="+c2 weight.to s+" c3="+c3 weight.to s+" c4="+c4 weight.to s+" t2t="+t2t.to s unless c4 weight > 0
end
}
68
A.3 Source Listing: build data stats.rb
# This script filters the Smart Arc RPH csv# files and builds some statistics from# the data.
# Define some constantsRPH DIR = "../data/smart arc csv"
STATS FILE = "../data/stats.txt"
# Column IndexesTIME STAMP = 0HEAT MWH = 1PON = 2POFF = 3I1 = 4I2 = 5I3 = 6SF1 = 7SF2 = 8SF3 = 9I2H1 = 10I2H2 = 11I2H3 = 12CHARGE NUM = 13LANCE O2 FLOW = 14I1SP = 15I2SP = 16I3SP = 17B1 GAS FLOW = 18CJ1 GAS FLOW = 19B2 GAS FLOW = 20CJ2 GAS FLOW = 21B1 O2 FLOW = 22CJ1 O2 FLOW = 23B2 O2 FLOW = 24CJ2 O2 FLOW = 25CJ1 C FLOW = 26CJ2 C FLOW = 27TOTAL GAS = 28TOTAL O2 = 29TOTAL C = 30C1 WEIGHT = 31C2 WEIGHT = 32
69
C3 WEIGHT = 33C4 WEIGHT = 34
rph csv files = Dir.entries(RPH DIR) [".", ".."]# Insert path for each filerph csv files.collect! {|f| RPH DIR + "/" + f}
# Open the destination filesoutfile = open(STATS FILE, "w")
def filter charge(charge1, charge2, charge3)stats = {}stats[’c1’] = charge1[ 1][C1 WEIGHT].to fstats[’c2’] = charge2[ 1][C2 WEIGHT].to fstats[’c3’] = charge3[ 1][C3 WEIGHT].to fstats[’ht size’] = charge1[ 1][C1 WEIGHT].to f +
charge2[ 1][C2 WEIGHT].to f + charge3[ 1][C3 WEIGHT].to fstats[’o2’] = charge3[ 1][TOTAL O2].to f ∗ 100stats[’carbon’] = charge3[ 1][TOTAL C].to fstats[’gas’] = charge3[ 1][TOTAL GAS].to f ∗ 100stats[’i2h’] = (charge3[ 1][I2H1].to f +
charge3[ 1][I2H2].to f + charge3[ 1][I2H3].to f)/3.0stats[’kwh’] = charge3[ 1][HEAT MWH].to f ∗ 1000# Add 5 minutes for tappingstats[’t2t’] = charge3[ 1][PON].to f + charge3[ 1][POFF].to f + 5.0
if ((stats[’ht size’] <= 70.0) or (stats[’ht size’] > 95.0))return(nil)
end
return(nil) if stats[’t2t’] > 250
return(stats)end
##################################################
stats = []
rph csv files.each {|f|
rph file = File.open(f, "r")rph file.gets() # Get rid of banner
charge1 = []
70
charge2 = []charge3 = []charge4 = []c1 weight = 0.0c2 weight = 0.0c3 weight = 0.0c4 weight = 0.0t2t = 0.0
rph file.each {|row|data = row.split(’,’)charge num = data[CHARGE NUM].to iif charge num == 1
c1 weight = data[C1 WEIGHT].to fcharge1 << data
elsif charge num == 2c2 weight = data[C2 WEIGHT].to fcharge2 << data
elsif charge num == 3c3 weight = data[C3 WEIGHT].to ft2t = data[PON].to f + data[POFF].to fcharge3 << data
elsif charge num == 4c4 weight = data[C4 WEIGHT].to fcharge4 << data
end
}
# ONLY TAKE THREE CHARGE HEATS FOR NOW# Filtering T2T time for mean value + 1 sigma# The cx != [] predicates are here because there# was at least one case where they swung the roof# and incremented the charge. This resulted in# c3 weight being ok, but the charge number was# 4, and therefore all of the charge3 data went in# to charge4. Just throw these away.if ((charge4 == []) and
(c1 weight > 15.0) and (c1 weight < 45.0) and
(c2 weight > 10.0) and (c2 weight < 45.0) and
(c3 weight > 0.0) and (c3 weight < 45.0) and
(t2t < 192) and (charge1 != []) and (charge2 != []) and
(charge3 != []))tmp = filter charge(charge1, charge2, charge3)stats << tmp unless tmp == nil
71
end
}
min c1 = 100.0max c1 = 0.0avg c1 = 0.0min c2 = 100.0max c2 = 0.0avg c2 = 0.0min c3 = 100.0max c3 = 0.0avg c3 = 0.0min htsz = 100.0max htsz = 0.0avg htsz = 0.0max o2 = 0.0min o2 = 100000avg o2 = 0.0avg o2pt = 0.0max gas = 0.0min gas = 100000avg gas = 0.0avg gaspt = 0.0min kwh = 100000max kwh = 0.0avg kwh = 0.0avg kwhpt = 0.0min carbon = 100000max carbon = 0.0avg carbon = 0.0avg carbonpt = 0.0min i2h = 100000max i2h = 0.0avg i2h = 0.0avg i2hpt = 0.0min t2t = 1000max t2t = 0.0avg t2t = 0.0min tph = 1000avg tph = 0.0max tph = 0.0
stats.each {|heat|avg c1 += heat[’c1’]
72
avg c2 += heat[’c2’]avg c3 += heat[’c3’]avg htsz += heat[’ht size’]avg gas += heat[’gas’]avg o2 += heat[’o2’]avg kwh += heat[’kwh’]avg carbon += heat[’carbon’]avg i2h += heat[’i2h’]avg t2t += heat[’t2t’]
min c1 = heat[’c1’] if heat[’c1’] < min c1min c2 = heat[’c2’] if heat[’c2’] < min c2min c3 = heat[’c3’] if heat[’c3’] < min c3min htsz = heat[’ht size’] if heat[’ht size’] < min htszmin gas = heat[’gas’] if heat[’gas’] < min gasmin o2 = heat[’o2’] if heat[’o2’] < min o2min kwh = heat[’kwh’] if heat[’kwh’] < min kwhmin carbon = heat[’carbon’] if heat[’carbon’] < min carbonmin i2h = heat[’i2h’] if heat[’i2h’] < min i2hmin t2t = heat[’t2t’] if heat[’t2t’] < min t2t
max c1 = heat[’c1’] if heat[’c1’] > max c1max c2 = heat[’c2’] if heat[’c2’] > max c2max c3 = heat[’c3’] if heat[’c3’] > max c3max htsz = heat[’ht size’] if heat[’ht size’] > max htszmax gas = heat[’gas’] if heat[’gas’] > max gasmax o2 = heat[’o2’] if heat[’o2’] > max o2max kwh = heat[’kwh’] if heat[’kwh’] > max kwhmax carbon = heat[’carbon’] if heat[’carbon’] > max carbonmax i2h = heat[’i2h’] if heat[’i2h’] > max i2hmax t2t = heat[’t2t’] if heat[’t2t’] > max t2t
}
num heats = stats.length()avg c1 /= num heatsavg c2 /= num heatsavg c3 /= num heatsavg htsz /= num heatsavg gas /= num heatsavg o2 /= num heatsavg carbon /= num heatsavg kwh /= num heatsavg i2h /= num heatsavg t2t /= num heats
73
avg tph = avg htsz / (avg t2t/60)
avg gaspt = avg gas / avg htszavg o2pt = avg o2 / avg htszavg kwhpt = avg kwh / avg htszavg carbonpt = avg carbon / avg htszavg i2hpt = avg i2h / avg htsz
o2 sd = 0.0gas sd = 0.0carbon sd = 0.0kwh sd = 0.0c1 sd = 0.0c2 sd = 0.0c3 sd = 0.0htsz sd = 0.0i2h sd = 0.0t2t sd = 0.0tph sd = 0.0
# Need Standard Deviationsstats.each {|heat|
o2 sd += (avg o2 heat[’o2’])∗∗2gas sd += (avg gas heat[’gas’])∗∗2carbon sd += (avg carbon heat[’carbon’])∗∗2kwh sd += (avg kwh heat[’kwh’])∗∗2c1 sd += (avg c1 heat[’c1’])∗∗2c2 sd += (avg c2 heat[’c2’])∗∗2c3 sd += (avg c3 heat[’c3’])∗∗2htsz sd += (avg htsz heat[’ht size’])∗∗2i2h sd += (avg i2h heat[’i2h’])∗∗2t2t sd += (avg t2t heat[’t2t’])∗∗2tph sd += (avg tph (60∗heat[’ht size’])/heat[’t2t’])∗∗2
}
o2 sd = (o2 sd / num heats)∗∗0.5gas sd = (gas sd / num heats)∗∗0.5carbon sd = (carbon sd / num heats)∗∗0.5kwh sd = (kwh sd / num heats)∗∗0.5c1 sd = (c1 sd / num heats)∗∗0.5c2 sd = (c2 sd / num heats)∗∗0.5c3 sd = (c3 sd / num heats)∗∗0.5htsz sd = (htsz sd / num heats)∗∗0.5i2h sd = (i2h sd / num heats)∗∗0.5
74
t2t sd = (t2t sd / num heats)∗∗0.5tph sd = (tph sd / num heats)∗∗0.5
def output(file, name, min, avg, max)file.puts("min "+name+" avg "+name+" max "+name+" = "+min.to s+
", "+avg.to s+", "+max.to s)end
outfile.puts("num heats = " + num heats.to s)output(outfile, "htsz", min htsz, avg htsz, max htsz)output(outfile, "c1", min c1, avg c1, max c1)output(outfile, "c2", min c2, avg c2, max c2)output(outfile, "c3", min c3, avg c3, max c3)output(outfile, "gas", min gas, avg gas, max gas)output(outfile, "o2", min o2, avg o2, max o2)output(outfile, "carbon", min carbon, avg carbon, max carbon)output(outfile, "kwh", min kwh, avg kwh, max kwh)output(outfile, "i2h", min i2h, avg i2h, max i2h)output(outfile, "t2t", min t2t, avg t2t, max t2t)outfile.puts("avg gaspt = " + avg gaspt.to s)outfile.puts("avg o2pt = " + avg o2pt.to s)outfile.puts("avg kwhpt = " + avg kwhpt.to s)outfile.puts("avg carbonpt = " + avg carbonpt.to s)outfile.puts("avg i2hpt = " + avg i2hpt.to s)outfile.puts("avg tph = " + avg tph.to s)outfile.puts("O2 Std Dev = " + o2 sd.to s)outfile.puts("gas Std Dev = " + gas sd.to s)outfile.puts("carbon Std Dev = " + carbon sd.to s)outfile.puts("kwh Std Dev = " + kwh sd.to s)outfile.puts("c1 Std Dev = " + c1 sd.to s)outfile.puts("c2 Std Dev = " + c2 sd.to s)outfile.puts("c3 Std Dev = " + c3 sd.to s)outfile.puts("htsz Std Dev = " + htsz sd.to s)outfile.puts("i2h Std Dev = " + i2h sd.to s)outfile.puts("t2t Std Dev = " + t2t sd.to s)outfile.puts("tph Std Dev = " + tph sd.to s)outfile.close
75
A.4 Source Listing: build eaf lin.m
% Load eaf data and build first order linear% model for each output.
dat = load(’../data/eaf data’);
% Data Columnskwh = dat(:,1);kwhz = dat(:,2);i1dt = dat(:,3);i2dt = dat(:,4);i3dt = dat(:,5);
pm = dat(:,6);pmz = dat(:,7);chargekwhpt = dat(:,8);heatkwhpt = dat(:,9);chargeo2pt = dat(:,10);heato2pt = dat(:,11);chargegaspt = dat(:,12);heatgaspt = dat(:,13);chargecpt = dat(:,14);heatcpt = dat(:,15);
i2h = dat(:,16);i2hz = dat(:,17);i12dt = dat(:,18);i22dt = dat(:,19);i32dt = dat(:,20);
pon = dat(:,21);poff = dat(:,22);
i1 = dat(:,37);i2 = dat(:,38);i3 = dat(:,39);
% KWH Modelkwh theta = [ones(size(dat(:,1))) pon]\kwh
% Percent Melted Modelpm theta = [chargekwhpt heatkwhpt chargeo2pt heato2pt chargegaspt heatgaspt chargecpt heatcpt
76
% i2h Modeli2h theta = [i1 i2 i3 pon]\i2h
save lin eaf model.mat kwh theta pm theta i2h thetasave ascii kwh theta.txt kwh thetasave ascii pm theta.txt pm thetasave ascii i2h theta.txt i2h theta
77
A.5 Source Listing: eval lin model.m
% This is a script to evaluate the accuracy% of the eaf linear model. It builds residuals% for each of the model outputs
dat = load(’../data/eaf data’);
% Data Columnskwh = dat(:,1);%kwhz = dat(:,2);i1dt = dat(:,3);i2dt = dat(:,4);i3dt = dat(:,5);
pm = dat(:,6);%pmz = dat(:,7);chargekwhpt = dat(:,8);heatkwhpt = dat(:,9);chargeo2pt = dat(:,10);heato2pt = dat(:,11);chargegaspt = dat(:,12);heatgaspt = dat(:,13);chargecpt = dat(:,14);heatcpt = dat(:,15);
i2h = dat(:,16);%i2hz = dat(:,17);i12dt = dat(:,18);i22dt = dat(:,19);i32dt = dat(:,20);
pon = dat(:,21);poff = dat(:,22);o2 = dat(:,23);gas = dat(:,24);carbon = dat(:,25);
cj1 o2 flow = dat(:,26);cj1 gas flow = dat(:,27);cj1 c flow = dat(:,28);cj2 o2 flow = dat(:,29);cj2 gas flow = dat(:,30);cj2 c flow = dat(:,31);
78
b1 o2 flow = dat(:,32);b1 gas flow = dat(:,33);b2 o2 flow = dat(:,34);b2 gas flow = dat(:,35);
i1 = dat(:,37);i2 = dat(:,38);i3 = dat(:,39);
load(’lin eaf model’);
kwh hat = 0.0;pm hat = 0.0;i2h hat = 0.0;o2 hat = 0.0;gas hat = 0.0;c hat = 0.0;
i2hz = 0.0;kwh r = zeros(1,length(kwh));pm r = zeros(1,length(pm));i2h r = zeros(1,length(i2h));o2 r = zeros(1,length(o2));gas r = zeros(1,length(gas));carbon r = zeros(1,length(carbon));
for j=2:1:length(kwh)
if kwh(j)<kwh(j 1)i2hz = 0.0;o2 hat = 0.0;gas hat = 0.0;c hat = 0.0;
end
o2 hat = o2 hat + (cj1 o2 flow(j)+cj2 o2 flow(j)+b1 o2 flow(j)+b2 o2 flow(j))∗0.0833;o2 r(j) = o2(j) o2 hat;
gas hat = gas hat + (cj1 gas flow(j)+cj2 gas flow(j)+b1 gas flow(j)+b2 gas flow(j))∗0.0833;gas r(j) = gas(j) gas hat;
c hat = c hat + (cj1 c flow(j)+cj2 c flow(j))∗0.0833;carbon r(j) = carbon(j) c hat;
79
a = [chargekwhpt(j) heatkwhpt(j) chargeo2pt(j) heato2pt(j) chargegaspt(j) heatgaspt(j) chargecpt(j)pm hat = a∗pm theta;pm r(j) = pm(j) pm hat;
a = [i1(j) i2(j) i3(j) pon(j)];i2h hat = a∗i2h theta;i2h r(j) = i2h(j) i2h hat;i2hz = i2h hat;
kwh hat = (kwh theta(1) + pon(j)∗kwh theta(2));kwh r(j) = kwh(j) kwh hat;
end
kwh rmse = sqrt(mean(kwh r.ˆ2))pm rmse = sqrt(mean(pm r.ˆ2))i2h rmse = sqrt(mean(i2h r.ˆ2))o2 rmse = sqrt(mean(o2 r.ˆ2))gas rmse = sqrt(mean(gas r.ˆ2))c rmse = sqrt(mean(carbon r.ˆ2))
% Evaluate the multiple correlation coefficient (squared) as detailed% in Ljung, p. 550.r2pm = 1 (sum(pm r.ˆ2)/sum(pm.ˆ2))r2kwh = 1 (sum(kwh r.ˆ2)/sum(kwh.ˆ2))r2i2h = 1 (sum(i2h r.ˆ2)/sum(i2h.ˆ2))r2o2 = 1 (sum(o2 r.ˆ2)/sum(o2.ˆ2))r2gas = 1 (sum(gas r.ˆ2)/sum(gas.ˆ2))r2carbon = 1 (sum(carbon r.ˆ2)/sum(carbon.ˆ2))
save lin eaf eval.mat kwh r kwh rmse pm r pm rmse i2h r i2h rmse o2 r o2 rmse gas r gas rmse
80
A.6 Source Listing: config.rb
# GA ConstantsPOOL SIZE = 70 # Must be evenNUMBER GENERATIONS = 70MUTATION PROB = 0.0001CROSSOVER PROB = 0.5TOURNAMENT SELECT CONST = 0.75ALLOW TWINS = false
# Fitness Function CoeficientsPERFORMANCE WEIGHT = 0.00COST WEIGHT = 1 PERFORMANCE WEIGHT
TAP TIME = 7.0FTA TIME = 7.0CHARGE TIMES = [2.0, 2.0, 2.0]DELTA T = 5.0/60.0 # 5 second update rate
# Attenuation FactorsGAS ATTEN FACTOR = 10.0
# Penalty FactorsRG = 0.1/1000RO2 = 0.1/2000RC = 0.1/100
# Min/Max Current Setpoints in KAMINI = 34.1MAXI = 48.09
# Maximum Carbon Jet Flow Rates#MAXCJGAS = 284#MAXCJO2 = 703#MAXCJCARBON = 78MAXCJGAS = 400MAXCJO2 = 600MAXCJCARBON = 50
# Maximum Burner Flow Rates#MAXBGAS = 356#MAXBO2 = 814MAXLANCEO2 = 1063MAXBGAS = 350
81
MAXBO2 = 600
# Max/Min Charge sizesMINC1 = 30.0MAXC1 = 40.0MINC2 = 25.0MAXC2 = 40.0MINC3 = 10.0MAXC3 = 35.0
AVG T2T = 1.73333
# Average Consumption NumbersMAXHEATSZ = 93.6MINHEATSZ = 75.4AVG HTSZ = 88.21AVG KWH = 33434AVG O2 = 55074AVG GAS = 18012AVG I2H = 2163AVG CARBON = 645AVG ELECTRODE = (103.6066∗AVG T2T)+(0.096454∗AVG I2H)
# Stand DeviationsO2 SD = 12630GAS SD = 4477CARBON SD = 229KWH SD = 1806HTSZ SD = 2.83I2H SD = 133T2T SD = 12.53TPH SD = 5.48
# Average Performance Numbers by charge tonAVG KWHPT = 379AVG ELECTRODEPT = AVG ELECTRODE/AVG HTSZAVG O2PT = 624AVG GASPT = 204AVG I2HPT = 24.52AVG CPT = 7.31AVG TPH = 50.78
# The following performance weights need# to add up to a total of approximately 1.0
82
PTPH WEIGHT = 0.167PKWHPT WEIGHT = 0.167PO2PT WEIGHT = 0.167PGASPT WEIGHT = 0.167PI2HPT WEIGHT = 0.167PCPT WEIGHT = 0.167
# Average CostsSCRAP COST = 193.0KWH COST = 0.0417O2 COST = 0.0042GAS COST = 0.0115662ELECTRODE COST = 1.3CARBON COST = 0.097AVG COST = (AVG HTSZ∗SCRAP COST) + (AVG KWH∗KWH COST) +
(AVG O2∗O2 COST) + (AVG GAS∗GAS COST) +(AVG ELECTRODE∗ELECTRODE COST) + (AVG CARBON∗CARBON COST)
AVG COSTPT = AVG COST/AVG HTSZ
EAF MODEL = {# Percent Melted model can be by charge, however,# the error accumulates and is actually higher than# using a single model for all three charges."pm"=>[
[6.3335697e 02, # Charge KWH/Ton4.3665308e 01, # Heat KWH/Ton8.8497605e 04, # Charge O2/Ton3.3451723e 03, # Heat O2/Ton1.4141620e 01, # Charge Gas/Ton6.2701272e 02, # Heat Gas/Ton4.2630796e 01, # Charge C/Ton1.1396741e+00, # Heat C/ton1.6893913e+00, # Pon4.6873351e 02], # Poff
[6.3335697e 02,4.3665308e 01,8.8497605e 04,3.3451723e 03,1.4141620e 01,6.2701272e 02,4.2630796e 01,1.1396741e+00,1.6893913e+00,4.6873351e 02],
83
[6.3335697e 02,4.3665308e 01,8.8497605e 04,3.3451723e 03,1.4141620e 01,6.2701272e 02,4.2630796e 01,1.1396741e+00,1.6893913e+00,4.6873351e 02]],
"kwh"=>[5.5893470e+02, # Offset4.3051941e+02], # Power On Time
"i2h"=>[4.2664467e 04, # i16.2387968e 04, # i22.4218623e 04, # i32.7094154e+01] # pon
}
84
A.7 Source Listing: eaf sim.rb
#!/usr/local/ruby/bin/ruby w
require ’eaf.rb’
require ’functions.rb’
require ’config.rb’
require ’eaf profile.rb’
require ’profile logger.rb’
# Build an EAF modellogger = Profile Logger.new()eaf = EAF.new(EAF MODEL, logger)
regulator = Regulator.new(3, nil)regulator.set profile([@C1 PP, @C2 PP, @C3 PP])
b1 = Burner.new(3, nil)b1.set profile([@C1 B1 PROFILE, @C2 B1 PROFILE, @C3 B1 PROFILE])b2 = Burner.new(3, nil)b2.set profile([@C1 B2 PROFILE, @C2 B2 PROFILE, @C3 B2 PROFILE])cj1 = CJet.new(3, nil)cj1.set profile([@C1 CJ1 PROFILE, @C2 CJ1 PROFILE, @C3 CJ1 PROFILE])cj2 = CJet.new(3, nil)cj2.set profile([@C1 CJ2 PROFILE, @C2 CJ2 PROFILE, @C3 CJ2 PROFILE])
lance = Lance.new(3, nil)lance.set profile(@LANCE PROFILE)
charges = Charges.new(3, nil)charges.set charges(@CHARGES)
# Run the heatmetrics = eaf.make heat(charges, regulator, b1, b2, cj1, cj2, lance)fitness = evaluate fitness(metrics)
cost delta = fitness[’cost delta’]tph delta = fitness[’tph delta’]o2pt delta = fitness[’o2pt delta’]kwhpt delta = fitness[’kwhpt delta’]electrodept delta = fitness[’electrodept delta’]electrode pt = fitness[’electrode pt’]gaspt delta = fitness[’gaspt delta’]i2hpt delta = fitness[’i2hpt delta’]
85
carbonpt delta = fitness[’carbonpt delta’]kwh delta = fitness[’kwh delta’]o2 delta = fitness[’o2 delta’]gas delta = fitness[’gas delta’]electrode delta = fitness[’electrode delta’]carbon delta = fitness[’carbon delta’]i2h delta = fitness[’i2h delta’]t2t delta = fitness[’t2t delta’]htsz delta = fitness[’htsz delta’]
puts(" METRICS ")puts "fitness = " + fitness[’fitness’].to sputs(" TIMES ")puts "pon = " + metrics[’pon’].to sputs "t2t = " + metrics[’t2t’].to sputs(" PERFORMANCE ")puts "tph = " + metrics[’tons per hour’].to sputs "kwh/ton = " + metrics[’kwh per ton’].to sputs "gas/ton = " + metrics[’gas per ton’].to sputs "o2/ton = " + metrics[’o2 per ton’].to sputs "c/ton = " + metrics[’carbon per ton’].to sputs "electrode/ton = " + electrode pt.to sputs "i2h/ton = " + metrics[’i2h per ton’].to sputs(" CONSUMPTIONS ")puts "ht size = " + metrics[’ht size’].to sputs "kwh = " + metrics[’kwh’].to sputs "gas = " + metrics[’gas’].to sputs "o2 = " + metrics[’o2’].to sputs "carbon = " + metrics[’carbon’].to sputs "i2h = " + metrics[’i2h’].to sputs(" DELTAS ")puts "costpt delta = " + cost delta.to sputs "tph delta = " + tph delta.to sputs "kwhpt delta = " + kwhpt delta.to sputs "gaspt delta = " + gaspt delta.to sputs "o2pt delta = " + o2pt delta.to sputs "i2hpt delta = " + i2hpt delta.to sputs "electrodept delta = " + electrodept delta.to sputs "carbonpt delta = " + carbonpt delta.to sputs "kwh delta = " + kwh delta.to sputs "o2 delta = " + o2 delta.to sputs "gas delta = " + gas delta.to sputs "electrode delta = " + electrode delta.to sputs "carbon delta = " + carbon delta.to s
86
puts "i2h delta = " + i2h delta.to sputs "t2t delta = " + t2t delta.to sputs "htsz delta = " + htsz delta.to sputs(" PROFILES ")puts(charges.to s())puts(regulator.to s())puts(b1.to s("Burner 1"))puts(b2.to s("Burner 2"))puts(cj1.to s("Carbon Jet 1"))puts(cj2.to s("Carbon Jet 2"))puts(lance.to s())
87
A.8 Source Listing: functions.rb
require ’config.rb’
def validate string(str)end
def myrand(lower, upper)delta = upper lowernum = lower + (rand() ∗ delta)return(num)
end
# Pads a binary string to size widthdef pad bin str(width, str)
return str if str.length() == widthif (str.length() > width)
puts "pad bin str: str.length() > width"
puts "str = " + strexit(1)
end
pad = width str.length()newstr = "0"∗pad + strreturn(newstr)
end
# Build 3 random sized chargesdef build rand charges()
c1 = 0.0c2 = 0.0c3 = 0.0ht size = 0.0while ht size < MINHEATSZ or ht size > MAXHEATSZ
c1 = myrand(MINC1, MAXC1)c2 = myrand(MINC2, MAXC2)c3 = myrand(MINC3, MAXC3)ht size = c1 + c2 + c3
end
c1 = (c1∗100).to ic1 = c1.to s(2)c1 = pad bin str(12, c1)c2 = (c2∗100).to ic2 = c2.to s(2)c2 = pad bin str(12, c2)
88
c3 = (c3∗100).to ic3 = c3.to s(2)c3 = pad bin str(12, c3)charge string = c1 + c2 + c3return(charge string)
end
# Generates a random power program for one chargedef build rand pwrprog()
num steps = rand(10) + 1 # Rand gives 0 9current sp = []kwh limit = []chrgkwh limit = []heatkwh limit = []
power prog = ""
# Considered placing heuristics here for bore in# current levels. The problem is that the profile# is subject to the crossover operation and# probably would not maintain the bore in current.# The best plan is probably just to let the# algorithm develop the profiles, and make small# changes to the best profiles as neccessary.
num steps.times {current sp << (myrand(MINI, MAXI)∗100).to ikwh limit << rand(2∗∗15)chrgkwh limit << rand(2∗∗12)heatkwh limit << rand(2∗∗10)
}kwh limit.sort!()chrgkwh limit.sort!()heatkwh limit.sort!()
num steps.times {|i|step = ""
step += pad bin str(13, current sp[i].to s(2))step += pad bin str(15, kwh limit[i].to s(2))step += pad bin str(12, chrgkwh limit[i].to s(2))step += pad bin str(10, heatkwh limit[i].to s(2))power prog += step
}
89
zero str = "0" ∗ (13+15+12+10)remainder = 10 num stepsremainder str = zero str ∗ remainder
power prog = power prog + remainder strreturn(power prog)
end
def build rand brnprog()num steps = rand(10) + 1 # Rand gives 0 9o2 sp = []gas sp = []kwh limit = []chrgkwh limit = []heatkwh limit = []
burner prog = ""
num steps.times {o2 sp << rand(MAXBO2)gas sp << rand(MAXBGAS)kwh limit << rand(2∗∗15)chrgkwh limit << rand(2∗∗12)heatkwh limit << rand(2∗∗10)
}kwh limit.sort!()chrgkwh limit.sort!()heatkwh limit.sort!()
num steps.times {|i|step = ""
step += pad bin str(10, o2 sp[i].to s(2))step += pad bin str(9, gas sp[i].to s(2))step += pad bin str(15, kwh limit[i].to s(2))step += pad bin str(12, chrgkwh limit[i].to s(2))step += pad bin str(10, heatkwh limit[i].to s(2))burner prog += step
}
zero str = "0" ∗ (10+9+15+12+10)remainder = 10 num stepsremainder str = zero str ∗ remainder
burner prog = burner prog + remainder str
90
return(burner prog)end
def build rand hjprog()num steps = rand(10) + 1 # Rand gives 0 9o2 sp = []gas sp = []carbon sp = []kwh limit = []chrgkwh limit = []heatkwh limit = []
burner prog = ""
num steps.times {o2 sp << rand(MAXCJO2)gas sp << rand(MAXCJGAS)carbon sp << rand(MAXCJCARBON)kwh limit << rand(2∗∗15)chrgkwh limit << rand(2∗∗12)heatkwh limit << rand(2∗∗10)
}kwh limit.sort!()chrgkwh limit.sort!()heatkwh limit.sort!()
num steps.times {|i|step = ""
step += pad bin str(10, o2 sp[i].to s(2))step += pad bin str(9, gas sp[i].to s(2))step += pad bin str(7, carbon sp[i].to s(2))step += pad bin str(15, kwh limit[i].to s(2))step += pad bin str(12, chrgkwh limit[i].to s(2))step += pad bin str(10, heatkwh limit[i].to s(2))burner prog += step
}
zero str = "0" ∗ (10+9+7+15+12+10)remainder = 10 num stepsremainder str = zero str ∗ remainder
burner prog = burner prog + remainder strreturn(burner prog)
91
end
def build rand lanceprog()tmp = [rand(100), rand(100)].sortpm start = pad bin str(7, tmp[0].to s(2))pm end = pad bin str(7, tmp[1].to s(2))prog = pm start + pm endreturn(prog)
end
# This build the control configuration and charges for# a heat.def build rand heat(num charges=3)
# Build 3 chargesheat = build rand charges()
# build 3 power programsheat << build rand pwrprog()heat << build rand pwrprog()heat << build rand pwrprog()
# build 3 burner programs for burner 1heat << build rand brnprog()heat << build rand brnprog()heat << build rand brnprog()
# build 3 burner programs for burner 2heat << build rand brnprog()heat << build rand brnprog()heat << build rand brnprog()
# build 3 burner programs for carbon jet 1heat << build rand hjprog()heat << build rand hjprog()heat << build rand hjprog()
# build 3 burner programs for carbon jet 2heat << build rand hjprog()heat << build rand hjprog()heat << build rand hjprog()
# build 3 lance programs
92
heat << build rand lanceprog()heat << build rand lanceprog()heat << build rand lanceprog()
return(heat)end
def get burner program(num charges, burner num, string)sub = ""
if num charges == 3 and burner num == 1sub = string[1536..3215]
elsif num charges == 3 and burner num == 2sub = string[3216..4895]
end
return(sub)end
def get power program(num charges, string)sub = ""
if num charges == 3sub = string[36..1535]
end
return(sub)end
def get hj program(num charges, burner num, string)sub = ""
if num charges == 3 and burner num == 1sub = string[4896..6785]
elsif num charges == 3 and burner num == 2sub = string[6786..8675]
end
return(sub)end
def get lance program(num charges, string)sub = ""
if num charges == 3sub = string[8676..8717]
end
return(sub)end
def get charges(num charges, string)
93
sub = ""
if num charges == 3sub = string[0..35]
end
return(sub)end
def evaluate fitness(metrics)fitness = {}kwh pt = metrics[’kwh per ton’]o2 pt = metrics[’o2 per ton’]gas pt = metrics[’gas per ton’]i2h pt = metrics[’i2h per ton’]carbon pt = metrics[’carbon per ton’]tph = metrics[’tons per hour’]
perf = (AVG TPH/tph)perf ∗= PERFORMANCE WEIGHT
ht sz = metrics[’ht size’]kwh = metrics[’kwh’]o2 = metrics[’o2’]gas = metrics[’gas’]i2h = metrics[’i2h’]carbon = metrics[’carbon’]t2t = metrics[’t2t’]electrode = (103.6066∗t2t/60.0)+(0.096454∗i2h)electrode pt = electrode/ht szfitness[’electrode pt’] = electrode ptcost = (ht sz∗SCRAP COST)+(kwh∗KWH COST)+(o2∗O2 COST)+
(gas∗GAS COST)+(electrode∗ELECTRODE COST)+(carbon∗CARBON COST)cost pt = cost/ht szfitness[’cost delta’] = cost pt AVG COSTPTcost pt /= AVG COSTPTcost pt ∗= COST WEIGHT
fitness[’fitness’] = 1.0/(perf+cost pt)
# Impose the penalty functionsif gas > (AVG GAS + GAS SD)
rg = RG∗((gas (AVG GAS+GAS SD))∗∗2)fitness[’fitness’] += rg
end
if o2 > (AVG O2 + O2 SD)
94
ro2 = RO2∗((o2 (AVG O2+O2 SD))∗∗2)fitness[’fitness’] += ro2
end
if carbon > (AVG CARBON + CARBON SD)rc = RC∗((carbon (AVG CARBON+CARBON SD))∗∗2)fitness[’fitness’] += rc
end
fitness[’tph delta’] = (tph AVG TPH)fitness[’o2pt delta’] = o2 pt AVG O2PTfitness[’kwhpt delta’] = kwh pt AVG KWHPTfitness[’gaspt delta’] = gas pt AVG GASPTfitness[’i2hpt delta’] = i2h pt AVG I2HPTfitness[’carbonpt delta’] = carbon pt AVG CPTfitness[’kwh delta’] = kwh AVG KWHfitness[’o2 delta’] = o2 AVG O2fitness[’gas delta’] = gas AVG GASfitness[’electrode delta’] = electrode AVG ELECTRODEfitness[’electrodept delta’] = electrode pt AVG ELECTRODEPTfitness[’carbon delta’] = carbon AVG CARBONfitness[’i2h delta’] = i2h AVG I2Hfitness[’t2t delta’] = t2t/60.0 AVG T2Tfitness[’htsz delta’] = ht sz AVG HTSZreturn(fitness)
end
# This crossover results in very low population# diversity.def channel crossover(string1, string2)
len = string1.length()k1 = rand(len)k2 = rand(len)new string = string1if k2 > k1
chunk = string2[k1..k2]new string[k1..k2] = chunk
elsif k1 > k2chunk = string2[k2..k1]new string[k2..k1] = chunk
else
new string = string1[0..k1]+string2[(k1+1)..(len 1)]end
return(new string)end
95
def normal crossover(str1, str2)len = str1.length()k = rand(len)child = str1[0..k]+str2[(k+1)..(len 1)]return(child)
end
# Using the simple weighted roulette wheel, or# Stochastic sampling with replacement.def selection(strings, fitness)
ftotal = 0.0fitness.each {|f| ftotal += f}mating pool = []indexes = []ev = []
fitness.each index {|i| ev[i] = fitness[i]/ftotal}sorted ev = ev.sort().reverse()ev total = 0.0ev.each {|e| ev total += e}
POOL SIZE.times {|i|x = rand()∗ev totaly = 0.0fitness.each index {|j|
y += sorted ev[j]if y >= x
index = ev.index(sorted ev[j])mating pool << strings[index]indexes << index
end
break if y >= x}
}
mating pool = mating pool.sort by {rand}#limit = mating pool.length()new pool = []0.step(POOL SIZE 2, 2) {|i|
str1, str2 = crossover(mating pool[i], mating pool[i+1])new pool << str1new pool << str2
}
96
new pool = new pool.sort by {rand}return(new pool)
end
def tournament selection(strings, fitness)parents = []len = strings.length()# This construct should result in a parent to child# ratio of approximately CROSSOVER PROB.len.times {
ind1 = rand(len)ind2 = rand(len)if rand() < CROSSOVER PROB
if rand() < TOURNAMENT SELECT CONSTif fitness[ind1] > fitness[ind2]
parents << strings[ind1]else
parents << strings[ind2]end
else
if fitness[ind1] < fitness[ind2]parents << strings[ind1]
else
parents << strings[ind2]end
end
end
}#parents = parents.uniq()return(parents)
end
def crossover(parents)children = []parents = parents.sort {rand}len = parents.length()while (children.length() + parents.uniq.length()) < POOL SIZE
str1 = parents[rand(len)]str2 = parents[rand(len)]str = normal crossover(str1, str2)children << strchildren = children.uniq() unless ALLOW TWINS
end
97
return([children, parents])end
def mutate(children)num mutations = 0strlen = children[0].length()children.each index {|i|
strlen.times {|bit|if rand() < MUTATION PROB
num mutations += 1if children[i][bit] == 49
children[i][bit] = "0"
else
children[i][bit] = "1"
end
end
}}return([num mutations, children])
end
98
A.9 Source Listing: eaf.rb
require ’config.rb’
class Regulatordef initialize(num charges, string)
unless string == nilc1 string = string[0..499]c2 string = string[500..999]c3 string = string[1000..1499]c4 string = string[1500..1999] if num charges == 4@c1 profile = []@c2 profile = []@c3 profile = []@c4 profile = []10.times {|i|
@c1 profile[i] = {"isp"=>c1 string[i∗50,13].to i(2)/100.0,"kwh limit"=>c1 string[(i∗50)+13,15].to i(2),"chrgkwhpt limit"=>c1 string[(i∗50)+28,12].to i(2),"heatkwhpt limit"=>c1 string[(i∗50)+40,10].to i(2)
}@c2 profile[i] = {
"isp"=>c2 string[i∗50,13].to i(2)/100.0,"kwh limit"=>c2 string[(i∗50)+13,15].to i(2),"chrgkwhpt limit"=>c2 string[(i∗50)+28,12].to i(2),"heatkwhpt limit"=>c2 string[(i∗50)+40,10].to i(2)
}@c3 profile[i] = {
"isp"=>c3 string[i∗50,13].to i(2)/100.0,"kwh limit"=>c3 string[(i∗50)+13,15].to i(2),"chrgkwhpt limit"=>c3 string[(i∗50)+28,12].to i(2),"heatkwhpt limit"=>c3 string[(i∗50)+40,10].to i(2)
}if num charges == 4
@c4 profile[i] = {"isp"=>c4 string[i∗50,13].to i(2)/100.0,"kwh limit"=>c4 string[(i∗50)+13,15].to i(2),"chrgkwhpt limit"=>c4 string[(i∗50)+28,12].to i(2),"heatkwhpt limit"=>c4 string[(i∗50)+40,10].to i(2)
}end
}end
99
@c1 steps = []@c2 steps = []@c3 steps = []@c4 steps = []
end
def set profile(profile)@c1 profile = profile[0]@c2 profile = profile[1]@c3 profile = profile[2]@c4 profile = profile[3] if profile.length() == 4
end
def to s()str = " Power Program \n"str += sprintf("%5s %10s %16s %16s\n", "Isp",
"KWH Limit", "ChrgKwhPt Limit", "HeatKwhPt Limit")@c1 steps.each {|step|
str += sprintf("%2.2f %10.0f %16.0f %16.0f\n", step["isp"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"@c2 steps.each {|step|
str += sprintf("%2.2f %10.0f %16.0f %16.0f\n", step["isp"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"@c3 steps.each {|step|
str += sprintf("%2.2f %10.0f %16.0f %16.0f\n", step["isp"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"return(str)
end
def update(charge num, kwh, chrgkwhpt, heatkwhpt)
profile steps = @c1 stepsprofile = @c1 profileif charge num == 1
profile = @c1 profileprofile steps = @c1 steps
elsif charge num == 2profile = @c2 profile
100
profile steps = @c2 stepselsif charge num == 3
profile = @c3 profileprofile steps = @c3 steps
elsif charge num == 4profile = @c4 profileprofile steps = @c4 steps
end
balance = [1.02, 0.98, 1]i1sp = profile[0]["isp"] ∗ balance[0]i2sp = profile[0]["isp"] ∗ balance[1]i3sp = profile[0]["isp"] ∗ balance[2]isp = profile[0]["isp"]
step = profile[0]profile.each index {|i|
step = profile[i]next if ((kwh > step[’kwh limit’]) or
(chrgkwhpt > step[’chrgkwhpt limit’]) or
(heatkwhpt > step[’heatkwhpt limit’])) and
(step[’isp’] > 0)if step["isp"] == 0
step = profile[i 1]end
isp = step["isp"]i1sp = step["isp"] ∗ balance[0]i2sp = step["isp"] ∗ balance[1]i3sp = step["isp"] ∗ balance[2]
# Get the first step in the arrayif profile steps == []
profile steps << {"isp"=>isp,"kwh limit"=>0,"chrgkwhpt limit"=>0,"heatkwhpt limit"=>0}
end
# Store the profile stepunless isp == profile steps[ 1]["isp"]
last step = profile[i 1]kwh = 0 unless kwh >= last step[’kwh limit’]chrgkwhpt = 0 unless chrgkwhpt >= last step[’chrgkwhpt limit’]heatkwhpt = 0 unless heatkwhpt >= last step[’heatkwhpt limit’]profile steps << {"isp"=>isp,
101
"kwh limit"=>kwh,"chrgkwhpt limit"=>chrgkwhpt,"heatkwhpt limit"=>heatkwhpt}
end
break
}
return([i1sp, i2sp, i3sp])end
end
class Burnerdef initialize(num charges, string)
unless string == nilc1 string = string[0..559]c2 string = string[560..1119]c3 string = string[1120..1679]c4 string = string[1680..2239] if num charges == 4@c1 profile = []@c2 profile = []@c3 profile = []@c4 profile = []10.times {|i|
@c1 profile[i] = {"o2 flow"=>c1 string[i∗56,10].to i(2),"gas flow"=>c1 string[(i∗56)+10,9].to i(2),"kwh limit"=>c1 string[(i∗56)+19,15].to i(2),"chrgkwhpt limit"=>c1 string[(i∗56)+34,12].to i(2),"heatkwhpt limit"=>c1 string[(i∗56)+46,10].to i(2)
}@c2 profile[i] = {
"o2 flow"=>c2 string[i∗56,10].to i(2),"gas flow"=>c2 string[(i∗56)+10,9].to i(2),"kwh limit"=>c2 string[(i∗56)+19,15].to i(2),"chrgkwhpt limit"=>c2 string[(i∗56)+34,12].to i(2),"heatkwhpt limit"=>c2 string[(i∗56)+46,10].to i(2)
}@c3 profile[i] = {
"o2 flow"=>c3 string[i∗56,10].to i(2),"gas flow"=>c3 string[(i∗56)+10,9].to i(2),"kwh limit"=>c3 string[(i∗56)+19,15].to i(2),"chrgkwhpt limit"=>c3 string[(i∗56)+34,12].to i(2),"heatkwhpt limit"=>c3 string[(i∗56)+46,10].to i(2)
}
102
if num charges == 4@c1 profile[i] = {
"o2 flow"=>c4 string[i∗56,10].to i(2),"gas flow"=>c4 string[(i∗56)+10,9].to i(2),"kwh limit"=>c4 string[(i∗56)+19,15].to i(2),"chrgkwhpt limit"=>c4 string[(i∗56)+34,12].to i(2),"heatkwhpt limit"=>c4 string[(i∗56)+46,10].to i(2)
}end
}end
@c1 steps = []@c2 steps = []@c3 steps = []@c4 steps = []
end
def set profile(profile)@c1 profile = profile[0]@c2 profile = profile[1]@c3 profile = profile[2]@c4 profile = profile[3] if profile.length() == 4
end
def to s(burner name)str = " " + burner name + " Profile \n"str += sprintf("%8s %9s %10s %16s %16s\n",
"O2 Flow", "Gas Flow","KWH Limit", "ChrgKwhPt Limit", "HeatKwhPt Limit")
@c1 steps.each {|step|str += sprintf("%8.0f %9.0f %10.0f %16.0f %16.0f\n",
step["o2 flow"], step["gas flow"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"@c2 steps.each {|step|
str += sprintf("%8.0f %9.0f %10.0f %16.0f %16.0f\n",step["o2 flow"], step["gas flow"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"@c3 steps.each {|step|
str += sprintf("%8.0f %9.0f %10.0f %16.0f %16.0f\n",step["o2 flow"], step["gas flow"],
103
step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"return(str)
end
def update(charge num, kwh, chrgkwhpt, heatkwhpt)
profile = @c1 profileprofile steps = @c1 stepsif charge num == 1
profile = @c1 profileprofile steps = @c1 steps
elsif charge num == 2profile = @c2 profileprofile steps = @c2 steps
elsif charge num == 3profile = @c3 profileprofile steps = @c3 steps
elsif charge num == 4profile = @c4 profileprofile steps = @c4 steps
end
o2 flow = 0.0gas flow = 0.0
profile.each index {|i|step = profile[i]next if ((kwh > step[’kwh limit’]) or
(chrgkwhpt > step[’chrgkwhpt limit’]) or
(heatkwhpt > step[’heatkwhpt limit’]))o2 flow = step["o2 flow"]gas flow = step["gas flow"]
# Get the first step in the arrayif profile steps == []
profile steps << {"o2 flow"=>o2 flow,"gas flow"=>gas flow,"kwh limit"=>0,"chrgkwhpt limit"=>0,"heatkwhpt limit"=>0}
end
# Store the profile step
104
unless o2 flow == profile steps[ 1]["o2 flow"] and
gas flow == profile steps[ 1]["gas flow"]last step = profile[i 1]kwh = 0 unless kwh >= last step[’kwh limit’]chrgkwhpt = 0 unless chrgkwhpt >= last step[’chrgkwhpt limit’]heatkwhpt = 0 unless heatkwhpt >= last step[’heatkwhpt limit’]profile steps << {"o2 flow"=>o2 flow,
"gas flow"=>gas flow,"kwh limit"=>kwh,"chrgkwhpt limit"=>chrgkwhpt,"heatkwhpt limit"=>heatkwhpt}
end
break
}
return([o2 flow, gas flow])end
end
class CJetdef initialize(num charges, string)
unless string == nilc1 string = string[0..629]c2 string = string[630..1259]c3 string = string[1260..1889]c4 string = string[1890..2519] if num charges == 4@c1 profile = []@c2 profile = []@c3 profile = []@c4 profile = []10.times {|i|
@c1 profile[i] = {"o2 flow"=>c1 string[i∗63,10].to i(2),"gas flow"=>c1 string[(i∗63)+10,9].to i(2),"c flow"=>c1 string[(i∗63)+19,7].to i(2),"kwh limit"=>c1 string[(i∗63)+26,15].to i(2),"chrgkwhpt limit"=>c1 string[(i∗63)+41,12].to i(2),"heatkwhpt limit"=>c1 string[(i∗63)+53,10].to i(2)
}@c2 profile[i] = {
"o2 flow"=>c2 string[i∗63,10].to i(2),"gas flow"=>c2 string[(i∗63)+10,9].to i(2),"c flow"=>c2 string[(i∗63)+19,7].to i(2),"kwh limit"=>c2 string[(i∗63)+26,15].to i(2),
105
"chrgkwhpt limit"=>c2 string[(i∗63)+41,12].to i(2),"heatkwhpt limit"=>c2 string[(i∗63)+53,10].to i(2)
}@c3 profile[i] = {
"o2 flow"=>c3 string[i∗63,10].to i(2),"gas flow"=>c3 string[(i∗63)+10,9].to i(2),"c flow"=>c3 string[(i∗63)+19,7].to i(2),"kwh limit"=>c3 string[(i∗63)+26,15].to i(2),"chrgkwhpt limit"=>c3 string[(i∗63)+41,12].to i(2),"heatkwhpt limit"=>c3 string[(i∗63)+53,10].to i(2)
}if num charges == 4
@c4 profile[i] = {"o2 flow"=>c4 string[i∗63,10].to i(2),"gas flow"=>c4 string[(i∗63)+10,9].to i(2),"c flow"=>c4 string[(i∗63)+19,7].to i(2),"kwh limit"=>c4 string[(i∗63)+26,15].to i(2),"chrgkwhpt limit"=>c4 string[(i∗63)+41,12].to i(2),"heatkwhpt limit"=>c4 string[(i∗63)+53,10].to i(2)
}end
}end
@c1 steps = []@c2 steps = []@c3 steps = []@c4 steps = []
end
def set profile(profile)@c1 profile = profile[0]@c2 profile = profile[1]@c3 profile = profile[2]@c4 profile = profile[3] if profile.length() == 4
end
def to s(burner name)str = " " + burner name + " Profile \n"str += sprintf("%8s %9s %7s %10s %16s %16s\n",
"O2 Flow", "Gas Flow", "C Flow","KWH Limit", "ChrgKwhPt Limit", "HeatKwhPt Limit")
@c1 steps.each {|step|str += sprintf("%8.0f %9.0f %6.0f %10.0f %16.0f %16.0f\n",
step["o2 flow"], step["gas flow"], step["c flow"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
106
}str += "\n"@c2 steps.each {|step|
str += sprintf("%8.0f %9.0f %6.0f %10.0f %16.0f %16.0f\n",step["o2 flow"], step["gas flow"], step["c flow"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"@c3 steps.each {|step|
str += sprintf("%8.0f %9.0f %6.0f %10.0f %16.0f %16.0f\n",step["o2 flow"], step["gas flow"], step["c flow"],step["kwh limit"], step["chrgkwhpt limit"],step["heatkwhpt limit"])
}str += "\n"return(str)
end
def update(charge num, kwh, chrgkwhpt, heatkwhpt)
profile = @c1 profileprofile steps = @c1 stepsif charge num == 1
profile = @c1 profileprofile steps = @c1 steps
elsif charge num == 2profile = @c2 profileprofile steps = @c2 steps
elsif charge num == 3profile = @c3 profileprofile steps = @c3 steps
elsif charge num == 4profile = @c4 profileprofile steps = @c4 steps
end
o2 flow = 0.0gas flow = 0.0c flow = 0.0
profile.each index {|i|step = profile[i]next if ((kwh > step[’kwh limit’]) or
(chrgkwhpt > step[’chrgkwhpt limit’]) or
107
(heatkwhpt > step[’heatkwhpt limit’]))o2 flow = step["o2 flow"]gas flow = step["gas flow"]c flow = step["c flow"]
# Get the first step in the arrayif profile steps == []
profile steps << {"o2 flow"=>o2 flow,"gas flow"=>gas flow,"c flow"=>c flow,"kwh limit"=>0,"chrgkwhpt limit"=>0,"heatkwhpt limit"=>0}
end
# Store the profile stepunless o2 flow == profile steps[ 1]["o2 flow"] and
gas flow == profile steps[ 1]["gas flow"] and
c flow == profile steps[ 1]["c flow"]last step = profile[i 1]kwh = 0 unless kwh >= last step[’kwh limit’]chrgkwhpt = 0 unless chrgkwhpt >= last step[’chrgkwhpt limit’]heatkwhpt = 0 unless heatkwhpt >= last step[’heatkwhpt limit’]profile steps << {"o2 flow"=>o2 flow,
"gas flow"=>gas flow,"c flow"=>c flow,"kwh limit"=>kwh,"chrgkwhpt limit"=>chrgkwhpt,"heatkwhpt limit"=>heatkwhpt}
end
break
}
return([o2 flow, gas flow, c flow])end
end
class Chargesdef initialize(num charges, string)
unless string == nil@charges = []@charges[0] = string[0..11].to i(2)/100.0@charges[1] = string[12..23].to i(2)/100.0@charges[2] = string[24..35].to i(2)/100.0@charges[3] = string[36..47].to i(2)/100.0 if num charges == 4
108
end
end
def set charges(charges)@charges = []@charges[0] = charges[0]@charges[1] = charges[1]@charges[2] = charges[2]@charges[3] = charges[3] if charges.length() == 4
end
def get charge weight(charge num)return(@charges[charge num 1])
end
def to s()str = " Charge Sizes \n"str += "Charge 1 = " + @charges[0].to s + " tons\n"str += "Charge 2 = " + @charges[1].to s + " tons\n"str += "Charge 3 = " + @charges[2].to s + " tons\n\n"return(str)
end
def each()@charges.each {|c| yield c}
end
end
class Lancedef initialize(num charges, string)
unless string == nilc1 string = string[0..13]@c1 profile = {"pm start"=>c1 string[0..6].to i(2),
"pm end"=>c1 string[7..13].to i(2)}c2 string = string[14..27]@c2 profile = {"pm start"=>c2 string[0..6].to i(2),
"pm end"=>c2 string[7..13].to i(2)}c3 string = string[28..41]@c3 profile = {"pm start"=>c3 string[0..6].to i(2),
"pm end"=>c3 string[7..13].to i(2)}@c4 profile = []if num charges == 4
c4 string = string[42..55]@c4 profile = {"pm start"=>c4 string[0..6].to i(2),
"pm end"=>c4 string[7..13].to i(2)}end
end
end
109
def set profile(profile)@c1 profile = profile[0]@c2 profile = profile[1]@c3 profile = profile[2]@c4 profile = profile[3] if profile.length() == 4
end
def to s()str = " Lance Program \n"str += "C1: " + @c1 profile["pm start"].to s + "% to " +
@c1 profile["pm end"].to s + "%\n"str += "C2: " + @c2 profile["pm start"].to s + "% to " +
@c2 profile["pm end"].to s + "%\n"str += "C3: " + @c3 profile["pm start"].to s + "% to " +
@c3 profile["pm end"].to s + "%\n\n"return(str)
end
def update(charge num, pm)profile = @c1 profileflow = 0.0if charge num == 1
profile = @c1 profileelsif charge num == 2
profile = @c2 profileelsif charge num == 3
profile = @c3 profileelsif charge num == 4
profile = @c4 profileend
if (pm > profile["pm start"]) and (pm < profile["pm end"])flow = 1063
end
return(flow)end
end
class EAFdef initialize(model, logger)
@pm coef = model[’pm’]@kwh coef = model[’kwh’]#@o2 coef = model[’o2’]#@gas coef = model[’gas’]#@carbon coef = model[’carbon’]@i2h coef = model[’i2h’]
110
@logger = loggerend
def update(inputs)i1sp = inputs[’i1sp’]i2sp = inputs[’i2sp’]i3sp = inputs[’i3sp’]cj1 o2 flow = inputs[’cj1 o2 flow’]cj1 gas flow = inputs[’cj1 gas flow’]cj1 c flow = inputs[’cj1 c flow’]cj2 o2 flow = inputs[’cj2 o2 flow’]cj2 gas flow = inputs[’cj2 gas flow’]cj2 c flow = inputs[’cj2 c flow’]b1 o2 flow = inputs[’b1 o2 flow’]b1 gas flow = inputs[’b1 gas flow’]b2 o2 flow = inputs[’b2 o2 flow’]b2 gas flow = inputs[’b2 gas flow’]#lance o2 flow = inputs[’lance o2 flow’]charge num = inputs[’charge num’]#charge weight = inputs[’charge weight’]pon = inputs[’pon’]poff = inputs[’poff’]kwhz = inputs[’kwhz’]pmz = inputs[’pmz’]i2hz = inputs[’i2hz’]i1dt = i1sp∗DELTA Ti2dt = i2sp∗DELTA Ti3dt = i3sp∗DELTA Ti12dt = i1sp∗i1sp∗DELTA T/60.0i22dt = i2sp∗i2sp∗DELTA T/60.0i32dt = i3sp∗i3sp∗DELTA T/60.0chargekwhpt = inputs[’chargekwhpt’]heatkwhpt = inputs[’heatkwhpt’]chargeo2pt = inputs[’chargeo2pt’]heato2pt = inputs[’heato2pt’]chargegaspt = inputs[’chargegaspt’]heatgaspt = inputs[’heatgaspt’]chargecpt = inputs[’chargecpt’]heatcpt = inputs[’heatcpt’]
# Compute outputsoutputs = Hash.new()
outputs[’kwh’] = @kwh coef[0] + pon∗@kwh coef[1]#atten factor = 1 (1.0/GAS ATTEN FACTOR)∗pmz
111
#atten factor = 0.0 if pmz > GAS ATTEN FACTORatten factor = 1.0
# Get the PM coefficients for the chargepm coef = @pm coef[charge num 1]outputs[’percent melted’] = (chargekwhpt∗pm coef[0]) +
(heatkwhpt∗pm coef[1]) +(chargeo2pt∗pm coef[2]) +(heato2pt∗pm coef[3]) +atten factor∗(chargegaspt∗pm coef[4]) +atten factor∗(heatgaspt∗pm coef[5]) +(chargecpt∗pm coef[6]) +(heatcpt∗pm coef[7]) +(pon∗pm coef[8]) +(poff∗pm coef[9])
outputs[’i2h’] = (i1sp∗@i2h coef[0]) +(i2sp∗@i2h coef[1]) +(i3sp∗@i2h coef[2]) +(pon∗@i2h coef[3])
total o2 flow = cj1 o2 flow + cj2 o2 flow + b1 o2 flow + b2 o2 flowtotal gas flow = cj1 gas flow + cj2 gas flow + b1 gas flow + b2 gas flowtotal c flow = cj1 c flow + cj2 c flow
outputs[’o2’] = total o2 flow∗DELTA Toutputs[’gas’] = total gas flow∗DELTA Toutputs[’carbon’] = total c flow∗DELTA T
return(outputs)end
def make heat(charges, regulator, b1, b2, cj1, cj2, lance)
pon = 0.0poff = FTA TIMEkwh = 0.0charge kwh base = 0.0charge o2 base = 0.0charge gas base = 0.0charge c base = 0.0percent melted = 0.0t2t = 0.0o2 = 0.0gas = 0.0carbon = 0.0
112
i2h = 0.0ht size = 0.0charge num = 1kwhz = 0.0i2hz = 0.0pmz = 0.0metrics = Hash.new()setpoints = Hash.new()
charges.each {|chrg|charge kwh base = kwhcharge kwh = 0.0charge o2 base = o2charge gas base = gascharge c base = carbonpercent melted = 0.0ht size += chrgpoff += CHARGE TIMES[charge num 1]
while (percent melted < 100)
chargekwhpt = charge kwh/chrgheatkwhpt = kwh/ht size
i1sp, i2sp, i3sp = regulator.update(charge num,charge kwh,chargekwhpt,heatkwhpt)
b1 o2 flow, b1 gas flow = b1.update(charge num,charge kwh,chargekwhpt,heatkwhpt)
b2 o2 flow, b2 gas flow = b2.update(charge num,charge kwh,chargekwhpt,heatkwhpt)
cj1 o2 flow, cj1 gas flow, cj1 c flow = cj1.update(charge num,charge kwh,chargekwhpt,heatkwhpt)
cj2 o2 flow, cj2 gas flow, cj2 c flow = cj2.update(charge num,charge kwh,chargekwhpt,heatkwhpt)
113
lance o2 flow = lance.update(charge num, percent melted)
setpoints[’i1sp’] = i1spsetpoints[’i2sp’] = i2spsetpoints[’i3sp’] = i3spsetpoints[’b1 o2 flow’] = b1 o2 flowsetpoints[’b1 gas flow’] = b1 gas flowsetpoints[’b2 o2 flow’] = b2 o2 flowsetpoints[’b2 gas flow’] = b2 gas flowsetpoints[’cj1 o2 flow’] = cj1 o2 flowsetpoints[’cj1 gas flow’] = cj1 gas flowsetpoints[’cj1 c flow’] = cj1 c flowsetpoints[’cj2 o2 flow’] = cj2 o2 flowsetpoints[’cj2 gas flow’] = cj2 gas flowsetpoints[’cj2 c flow’] = cj2 c flowsetpoints[’lance o2 flow’] = lance o2 flowsetpoints[’charge num’] = charge numsetpoints[’charge kwh’] = charge kwhsetpoints[’chargekwhpt’] = chargekwhptsetpoints[’heatkwhpt’] = [email protected](setpoints)
pon += DELTA Tt2t = pon + poffcharge kwh = kwh charge kwh basecharge o2 = o2 charge o2 basecharge gas = gas charge gas basecharge c = carbon charge c base
kwhz = kwhpmz = percent meltedi2hz = i2h
inputs = Hash.new()inputs[’i1sp’] = i1sp∗1000.0inputs[’i2sp’] = i2sp∗1000.0inputs[’i3sp’] = i3sp∗1000.0inputs[’cj1 o2 flow’] = cj1 o2 flowinputs[’cj1 gas flow’] = cj1 gas flowinputs[’cj1 c flow’] = cj1 c flowinputs[’cj2 o2 flow’] = cj2 o2 flowinputs[’cj2 gas flow’] = cj2 gas flowinputs[’cj2 c flow’] = cj2 c flowinputs[’b1 o2 flow’] = b1 o2 flow
114
inputs[’b1 gas flow’] = b1 gas flowinputs[’b2 o2 flow’] = b2 o2 flowinputs[’b2 gas flow’] = b2 gas flowinputs[’lance o2 flow’] = lance o2 flowinputs[’charge num’] = charge num#inputs[’charge weight’] = chrginputs[’pon’] = poninputs[’poff’] = poffinputs[’chargekwhpt’] = chargekwhptinputs[’heatkwhpt’] = heatkwhptinputs[’chargeo2pt’] = charge o2/chrginputs[’heato2pt’] = o2/ht sizeinputs[’chargegaspt’] = charge gas/chrginputs[’heatgaspt’] = gas/ht sizeinputs[’chargecpt’] = charge c/chrginputs[’heatcpt’] = carbon/ht sizeinputs[’kwhz’] = kwhzinputs[’pmz’] = pmzinputs[’i2hz’] = i2hz
outputs = self.update(inputs)percent melted = outputs[’percent melted’]kwh = outputs[’kwh’]i2h = outputs[’i2h’]o2 += outputs[’o2’]gas += outputs[’gas’]carbon += outputs[’carbon’]
end
charge num += 1}poff += TAP TIMEt2t = pon + poff
electrode = (103.6066∗t2t/60.0)+(0.096454∗i2h)cost = (ht size∗SCRAP COST)+(kwh∗KWH COST)+(o2∗O2 COST)+
(gas∗GAS COST)+(electrode∗ELECTRODE COST)+(carbon∗CARBON COST)cost pt = cost/ht size
metrics[’cost pt’] = cost ptmetrics[’pon’] = ponmetrics[’poff’] = poffmetrics[’t2t’] = t2tmetrics[’kwh’] = kwh
115
metrics[’o2’] = o2metrics[’gas’] = gasmetrics[’carbon’] = carbonmetrics[’i2h’] = i2hmetrics[’electrode per ton’] = electrode/ht sizemetrics[’ht size’] = ht sizemetrics[’tons per hour’] = ht size/(t2t/60)metrics[’kwh per ton’] = kwh/ht sizemetrics[’o2 per ton’] = o2/ht sizemetrics[’gas per ton’] = gas/ht sizemetrics[’carbon per ton’] = carbon/ht sizemetrics[’i2h per ton’] = i2h/ht size
return metricsend
end
116
A.10 Source Listing: eaf profile.rb
# Profiles
# Charges@CHARGES = [38, 30, 14.2]
@C1 PP = [{"isp"=>47.15, "kwh limit"=>50000, "chrgkwhpt limit"=>25,"heatkwhpt limit"=>50000},{"isp"=>39.55, "kwh limit"=>50000, "chrgkwhpt limit"=>50,"heatkwhpt limit"=>50000},{"isp"=>36.5, "kwh limit"=>50000, "chrgkwhpt limit"=>100,"heatkwhpt limit"=>50000},{"isp"=>35, "kwh limit"=>50000, "chrgkwhpt limit"=>175,"heatkwhpt limit"=>50000},{"isp"=>38, "kwh limit"=>50000, "chrgkwhpt limit"=>225,"heatkwhpt limit"=>50000},{"isp"=>45, "kwh limit"=>50000, "chrgkwhpt limit"=>250,"heatkwhpt limit"=>50000},{"isp"=>0, "kwh limit"=>0, "chrgkwhpt limit"=>0,"heatkwhpt limit"=>0}
]
@C2 PP = [{"isp"=>47.15, "kwh limit"=>50000, "chrgkwhpt limit"=>25,"heatkwhpt limit"=>50000},{"isp"=>39.55, "kwh limit"=>50000, "chrgkwhpt limit"=>50,"heatkwhpt limit"=>50000},{"isp"=>36.5, "kwh limit"=>50000, "chrgkwhpt limit"=>100,"heatkwhpt limit"=>50000},{"isp"=>35, "kwh limit"=>50000, "chrgkwhpt limit"=>175,"heatkwhpt limit"=>50000},{"isp"=>38, "kwh limit"=>50000, "chrgkwhpt limit"=>225,"heatkwhpt limit"=>50000},{"isp"=>45, "kwh limit"=>50000, "chrgkwhpt limit"=>250,"heatkwhpt limit"=>50000},{"isp"=>0, "kwh limit"=>0, "chrgkwhpt limit"=>0,"heatkwhpt limit"=>0}
]
@C3 PP = [{"isp"=>47.15, "kwh limit"=>50000, "chrgkwhpt limit"=>25,"heatkwhpt limit"=>50000},
117
{"isp"=>39.55, "kwh limit"=>50000, "chrgkwhpt limit"=>50,"heatkwhpt limit"=>50000},{"isp"=>36.5, "kwh limit"=>50000, "chrgkwhpt limit"=>100,"heatkwhpt limit"=>50000},{"isp"=>35, "kwh limit"=>50000, "chrgkwhpt limit"=>175,"heatkwhpt limit"=>50000},{"isp"=>38, "kwh limit"=>50000, "chrgkwhpt limit"=>225,"heatkwhpt limit"=>50000},{"isp"=>45, "kwh limit"=>50000, "chrgkwhpt limit"=>250,"heatkwhpt limit"=>50000},{"isp"=>0, "kwh limit"=>0, "chrgkwhpt limit"=>0,"heatkwhpt limit"=>0}
]
# Burner 1 Charge 1@C1 B1 PROFILE = [
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0}
]
# Burner 1 Charge 2@C2 B1 PROFILE = [
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>50000, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0}
]
# Burner 1 Charge 3@C3 B1 PROFILE = [
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>50000, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0}
]
# CJ1 Charge 1@C1 CJ1 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70, "c flow"=>0,
118
"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140, "c flow"=>0,"kwh limit"=>4000, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>600, "gas flow"=>75, "c flow"=>0,"kwh limit"=>4400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>600, "gas flow"=>75, "c flow"=>30,"kwh limit"=>7200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0, "c flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0},
]
# CJ1 Charge 2@C2 CJ1 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70, "c flow"=>0,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140, "c flow"=>0,"kwh limit"=>3200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>600, "gas flow"=>75, "c flow"=>30,"kwh limit"=>5200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>600, "gas flow"=>75, "c flow"=>20,"kwh limit"=>7200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0, "c flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0},
]
# CJ1 Charge 3@C3 CJ1 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70, "c flow"=>0,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140, "c flow"=>0,"kwh limit"=>1600, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>600, "gas flow"=>75, "c flow"=>30,"kwh limit"=>3200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
119
{"o2 flow"=>600, "gas flow"=>50, "c flow"=>30,"kwh limit"=>3600, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>600, "gas flow"=>50, "c flow"=>20,"kwh limit"=>5200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0, "c flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0},
]
# Burner 2 Charge 1@C1 B2 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140,"kwh limit"=>5200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>80, "gas flow"=>40,"kwh limit"=>6000, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>50000, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
]
# Burner 2 Charge 2@C2 B2 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140,"kwh limit"=>3200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>80, "gas flow"=>40,"kwh limit"=>7200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0}
]
# Burner 2 Charge 3@C3 B2 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
120
{"o2 flow"=>360, "gas flow"=>140,"kwh limit"=>1600, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>80, "gas flow"=>40,"kwh limit"=>5200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0,"kwh limit"=>0, "chrgkwhpt limit"=>0, "heatkwhpt limit"=>0}
]
# CJ2 Charge 1@C1 CJ2 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70, "c flow"=>0,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140, "c flow"=>0,"kwh limit"=>5200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>400, "gas flow"=>40, "c flow"=>0,"kwh limit"=>7200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0, "c flow"=>0, "kwh limit"=>0,"chrgkwhpt limit"=>0, "heatkwhpt limit"=>0},
]
# CJ2 Charge 2@C2 CJ2 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70, "c flow"=>0,"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140, "c flow"=>0,"kwh limit"=>3200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>400, "gas flow"=>40, "c flow"=>0,"kwh limit"=>7200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0, "c flow"=>0, "kwh limit"=>0,"chrgkwhpt limit"=>0, "heatkwhpt limit"=>0},
]
# CJ2 Charge 3@C3 CJ2 PROFILE = [
{"o2 flow"=>140, "gas flow"=>70, "c flow"=>0,
121
"kwh limit"=>400, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>360, "gas flow"=>140, "c flow"=>0,"kwh limit"=>1600, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>400, "gas flow"=>40, "c flow"=>0,"kwh limit"=>5200, "chrgkwhpt limit"=>50000, "heatkwhpt limit"=>50000},
{"o2 flow"=>0, "gas flow"=>0, "c flow"=>0, "kwh limit"=>0,"chrgkwhpt limit"=>0, "heatkwhpt limit"=>0},
]
@LANCE PROFILE = [{"pm start"=>0, "pm end"=>0},{"pm start"=>0, "pm end"=>0},{"pm start"=>0, "pm end"=>0}
]
122
A.11 Source Listing: ga eaf sim.rb
#!/usr/local/ruby/bin/ruby w
require ’eaf.rb’
require ’functions.rb’
require ’config.rb’
require ’profile logger.rb’
include GC
GC.start
strings = []fitness = []max fitness = 100000metrics = {}
# Build an EAF modellogger = Profile Logger.new()eaf = EAF.new(EAF MODEL, logger)
# Seed the random number generatorsrand(Time.new.to i)
POOL SIZE.times {# Build random control profilesstrings << build rand heat()
}
start time = Time.new()
File.open("algorithm performance.txt", "a") {|f|f.puts("\n# Start Time: " + start time.to s)f.puts("# Pool Size = " + POOL SIZE.to s)f.puts("# Mutation Rate = " + MUTATION PROB.to s)f.puts("# Tournament Selection")f.puts("# TPH Weight = " + PERFORMANCE WEIGHT.to s +
" Cost Weight = " + COST WEIGHT.to s)}
perf = File.open("perf", "a")
NUMBER GENERATIONS.times {|generation|
123
ht size = []o2pt = []gaspt = []carbonpt = []kwhpt = []electrodept = []tons per hour = []cost pt = []
fitness = []strings.each {|string|
# Get the chargescharge string = get charges(3, string)charges = Charges.new(3, charge string)
# Get the power programpp string = get power program(3, string)regulator = Regulator.new(3, pp string)
# Get the burner programbp1 string = get burner program(3, 1, string)b1 = Burner.new(3, bp1 string)
# Get the burner programbp2 string = get burner program(3, 2, string)b2 = Burner.new(3, bp2 string)
# Get the carbon jet programhj1 string = get hj program(3, 1, string)hj1 = CJet.new(3, hj1 string)
# Get the carbon jet programhj2 string = get hj program(3, 2, string)hj2 = CJet.new(3, hj2 string)
# Get the manipulator lance programlp string = get lance program(3, string)lance = Lance.new(3, lp string)
# Run the heatmetrics = eaf.make heat(charges, regulator, b1, b2, hj1, hj2, lance)
# Put together the equipment hash for the logger. Have
124
# to do this after the heat to see which profile steps# were actualy used.equipment = {}equipment[’regulator’] = regulatorequipment[’b1’] = b1equipment[’b2’] = b2equipment[’hj1’] = hj1equipment[’hj2’] = hj2equipment[’lance’] = lanceequipment[’charges’] = charges
# Evaluate the performancestring fitness = evaluate fitness(metrics)fitness << string fitness[’fitness’]ht size << metrics[’ht size’]o2pt << metrics[’o2 per ton’]gaspt << metrics[’gas per ton’]carbonpt << metrics[’carbon per ton’]kwhpt << metrics[’kwh per ton’]electrodept << metrics[’electrode per ton’]tons per hour << metrics[’tons per hour’]cost pt << metrics[’cost pt’]
# Save to file if the bestif string fitness[’fitness’] > max fitness
max fitness = string fitness[’fitness’]logger.log(equipment, start time, string fitness, metrics)
end
}
f bar = 0.0ht size bar = 0.0o2pt bar = 0.0gaspt bar = 0.0carbonpt bar = 0.0kwhpt bar = 0.0electrodept bar = 0.0tph bar = 0.0costpt bar = 0.0len = strings.length()len.times {|i|
f bar += fitness[i]ht size bar += ht size[i]o2pt bar += o2pt[i]
125
gaspt bar += gaspt[i]carbonpt bar += carbonpt[i]kwhpt bar += kwhpt[i]electrodept bar += electrodept[i]tph bar += tons per hour[i]costpt bar += cost pt[i]
}f bar /= lentph bar /= lencostpt bar /= lenht size bar /= leno2pt bar /= lengaspt bar /= lencarbonpt bar /= lenkwhpt bar /= lenelectrodept bar /= lenperf.puts(generation.to s + " " + f bar.to s + " " + tph bar.to s + " " +
costpt bar.to s + " " + ht size bar.to s + " " + kwhpt bar.to s +" " + o2pt bar.to s + " " + gaspt bar.to s + " " +carbonpt bar.to s + " " + electrodept bar.to s)
uniquness = strings.uniq.length().to f/POOL SIZE.to fputs "generation ##{generation} fbar = " + f bar.to s +
" uniquness = " + uniquness.to sFile.open("algorithm performance.txt", "a") {|f|
f.puts("generation ##{generation} fbar = " + f bar.to s)}
parents = tournament selection(strings, fitness)children, parents = crossover(parents)num mutations, children = mutate(children)strings = children + parents
#num u strings = strings.uniq.length()#puts ”num strings = ” + strings.length().to s +# ” ” + ”Unique strings = #{num u strings}, num mutations” +# ” = #{num mutations}”
garbage collect}perf.close
126
A.12 Source Listing: profile logger.rb
# Just a class to log the setpoints throughout# the heat.require ’config.rb’
class Profile Loggerdef initialize()end
def update(setpoints)end
def log(equipment, start time, string fitness, metrics)
best file string = start time.strftime("%d%m%H%M%Y")filename = "./profiles/best profile "+best file string+".txt"
# Get the delta’scost delta = string fitness[’cost delta’]tph delta = string fitness[’tph delta’]o2pt delta = string fitness[’o2pt delta’]kwhpt delta = string fitness[’kwhpt delta’]gaspt delta = string fitness[’gaspt delta’]i2hpt delta = string fitness[’i2hpt delta’]carbonpt delta = string fitness[’carbonpt delta’]kwh delta = string fitness[’kwh delta’]o2 delta = string fitness[’o2 delta’]gas delta = string fitness[’gas delta’]electrode delta = string fitness[’electrode delta’]carbon delta = string fitness[’carbon delta’]i2h delta = string fitness[’i2h delta’]t2t delta = string fitness[’t2t delta’]htsz delta = string fitness[’htsz delta’]
electrode pt = string fitness[’electrode pt’]electrodept delta = string fitness[’electrodept delta’]
f = File.open(filename, "w")f.puts(" SETUP ")f.puts("Start Time: " + start time.to s)f.puts("Pool Size = " + POOL SIZE.to s)f.puts("Mutation Rate = " + MUTATION PROB.to s)f.puts("Tournament Selection")f.puts("TPH Weight = " + PERFORMANCE WEIGHT.to s +
" Cost Weight = " + COST WEIGHT.to s)
127
f.puts(" METRICS ")f.puts "fitness = " + string fitness[’fitness’].to sf.puts(" TIMES ")f.puts "pon = " + metrics[’pon’].to sf.puts "t2t = " + metrics[’t2t’].to sf.puts(" PERFORMANCE ")f.puts "tph = " + metrics[’tons per hour’].to sf.puts "kwh/ton = " + metrics[’kwh per ton’].to sf.puts "o2/ton = " + metrics[’o2 per ton’].to sf.puts "gas/ton = " + metrics[’gas per ton’].to sf.puts "c/ton = " + metrics[’carbon per ton’].to sf.puts "electrode/ton = " + electrode pt.to sf.puts "i2h/ton = " + metrics[’i2h per ton’].to sf.puts(" CONSUMPTIONS ")f.puts "ht size = " + metrics[’ht size’].to sf.puts "kwh = " + metrics[’kwh’].to sf.puts "gas = " + metrics[’gas’].to sf.puts "o2 = " + metrics[’o2’].to sf.puts "carbon = " + metrics[’carbon’].to sf.puts "i2h = " + metrics[’i2h’].to sf.puts(" DELTAS ")f.puts "costpt delta = " + cost delta.to sf.puts "tph delta = " + tph delta.to sf.puts "htsz delta = " + htsz delta.to sf.puts "kwhpt delta = " + kwhpt delta.to sf.puts "gaspt delta = " + gaspt delta.to sf.puts "o2pt delta = " + o2pt delta.to sf.puts "electrodept delta = " + electrodept delta.to sf.puts "i2hpt delta = " + i2hpt delta.to sf.puts "carbonpt delta = " + carbonpt delta.to sf.puts "kwh delta = " + kwh delta.to sf.puts "o2 delta = " + o2 delta.to sf.puts "gas delta = " + gas delta.to sf.puts "electrode delta = " + electrode delta.to sf.puts "carbon delta = " + carbon delta.to sf.puts "i2h delta = " + i2h delta.to sf.puts "t2t delta = " + t2t delta.to sf.puts(" PROFILES ")f.puts(equipment[’charges’].to s())f.puts(equipment[’regulator’].to s())f.puts(equipment[’b1’].to s("Burner 1"))f.puts(equipment[’b2’].to s("Burner 2"))f.puts(equipment[’hj1’].to s("Carbon Jet 1"))f.puts(equipment[’hj2’].to s("Carbon Jet 2"))
128
f.puts(equipment[’lance’].to s())f.close()
end
end
129
A.13 Simulator Output
------- METRICS -------
fitness = 0.988666244185205
------- TIMES ------
pon = 74.5000000000001
t2t = 96.6000000000001
------- PERFORMANCE ------
tph = 49.6832298136645
kwh/ton = 393.983764783099
gas/ton = 157.045672375713
o2/ton = 605.117306329958
c/ton = 7.99058215610284
electrode/ton = 4.4966163069646
i2h/ton = 24.999199763908
------- CONSUMPTIONS ------
ht_size = 79.99
kwh = 31514.7613450001
gas = 12562.0833333333
o2 = 48403.3333333333
carbon = 639.166666666666
i2h = 1999.685989115
------- DELTAS ------
costpt_delta = 0.187650709229132
tph_delta = -1.09677018633547
kwhpt_delta = 14.9837647830985
gaspt_delta = -46.9543276242873
o2pt_delta = -18.8826936700425
i2hpt_delta = 0.479199763908028
electrodept_delta = 0.0955911399994074
carbonpt_delta = 0.680582156102838
kwh_delta = -1919.23865499995
o2_delta = -6670.66666666669
gas_delta = -5449.91666666674
electrode_delta = -28.5300915839013
carbon_delta = -5.83333333333394
i2h_delta = -163.314010884997
t2t_delta = -0.123329999999998
htsz_delta = -8.21999999999998
------ PROFILES -------
----- Charge Sizes -------
Charge 1 = 36.7 tons
Charge 2 = 28.1 tons
Charge 3 = 15.19 tons
130
------- Power Program -------
Isp KWH_Limit ChrgKwhPt_Limit HeatKwhPt_Limit
47.15 0 0 0
39.55 0 26 0
36.50 0 50 0
35.00 0 100 0
38.00 0 175 0
45.00 0 225 0
47.15 0 0 0
39.55 0 26 0
36.50 0 51 0
35.00 0 101 0
38.00 0 176 0
45.00 0 226 0
47.15 0 0 0
39.55 0 26 0
36.50 0 52 0
35.00 0 102 0
38.00 0 177 0
45.00 0 227 0
------- Burner 1 Profile --------
O2_Flow Gas_Flow KWH_Limit ChrgKwhPt_Limit HeatKwhPt_Limit
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
------- Burner 2 Profile --------
O2_Flow Gas_Flow KWH_Limit ChrgKwhPt_Limit HeatKwhPt_Limit
140 70 0 0 0
360 140 410 0 0
80 40 5217 0 0
0 0 6006 0 0
140 70 0 0 0
360 140 431 0 0
80 40 3229 0 0
140 70 0 0 0
131
360 140 431 0 0
80 40 1614 0 0
------- Carbon Jet 1 Profile --------
O2_Flow Gas_Flow C_Flow KWH_Limit ChrgKwhPt_Limit HeatKwhPt_Limit
140 70 0 0 0 0
360 140 0 410 0 0
600 75 0 4033 0 0
600 75 30 4428 0 0
140 70 0 0 0 0
360 140 0 431 0 0
600 75 30 3229 0 0
600 75 20 5202 0 0
140 70 0 0 0 0
360 140 0 431 0 0
600 75 30 1614 0 0
600 50 30 3229 0 0
600 50 20 3624 0 0
------- Carbon Jet 2 Profile --------
O2_Flow Gas_Flow C_Flow KWH_Limit ChrgKwhPt_Limit HeatKwhPt_Limit
140 70 0 0 0 0
360 140 0 410 0 0
400 40 0 5217 0 0
140 70 0 0 0 0
360 140 0 431 0 0
400 40 0 3229 0 0
140 70 0 0 0 0
360 140 0 431 0 0
400 40 0 1614 0 0
------ Lance Program ------
C1: 0% to 0%
C2: 0% to 0%
C3: 0% to 0%
132
REFERENCES
[1] David E. Goldberg. Genetic Algorithms in Search, Optimization and MachineLearning. Addison Wesley Longman Inc., first edition, 1989.
[2] Lennart Ljung. System Identification, Theory For The User. Prentice Hall Inc.,Upper Saddle River, New Jersey, second edition, 1999.
[3] Melanie Mitchell. An Introduction To Genetic Algorithms. Massachussetts Institueof Technology, Boston, Massachussetts, first edition, 1998.
[4] Oliver Nelles. Nonlinear System Identification. Sringer, Kronberg Germany, firstedition, 2001.