Language selection

Search

Patent 2415685 Summary

Third-party information liability

Some of the information on this Web page has been provided by external sources. The Government of Canada is not responsible for the accuracy, reliability or currency of the information supplied by external sources. Users wishing to rely upon this information should consult directly with the source of the information. Content provided by external sources is not subject to official languages, privacy and accessibility requirements.

Claims and Abstract availability

Any discrepancies in the text and image of the Claims and Abstract are due to differing posting times. Text of the Claims and Abstract are posted:

  • At the time the application is open to public inspection;
  • At the time of issue of the patent (grant).
(12) Patent: (11) CA 2415685
(54) English Title: APPARATUS AND METHOD FOR AUTOMATICALLY ACHIEVING AND MAINTAINING CONGRUENT CONTROL IN AN INDUSTRIAL BOILER
(54) French Title: APPAREIL ET METHODE POUR L'OBTENTION ET LE MAINTIEN AUTOMATIQUES DE LA REGULATION DANS UNE CHAUDIERE INDUSTRIELLE
Status: Expired
Bibliographic Data
(51) International Patent Classification (IPC):
  • G05D 11/08 (2006.01)
  • F22B 37/00 (2006.01)
(72) Inventors :
  • GUNTHER, JOHN C. (United States of America)
  • BOYETTE, SCOTT M. (United States of America)
  • THUNGSTROM, ERIC A. (United States of America)
  • WORRELL, NORMAN B. (United States of America)
  • BURGMAYER, PAUL R. (United States of America)
(73) Owners :
  • BETZDEARBORN INC. (United States of America)
(71) Applicants :
  • BETZDEARBORN INC. (United States of America)
(74) Agent: BORDEN LADNER GERVAIS LLP
(74) Associate agent:
(45) Issued: 2003-12-23
(22) Filed Date: 1995-08-24
(41) Open to Public Inspection: 1996-04-12
Examination requested: 2003-02-04
Availability of licence: N/A
(25) Language of filing: English

Patent Cooperation Treaty (PCT): No

(30) Application Priority Data:
Application No. Country/Territory Date
08/321,338 United States of America 1994-10-11

Abstracts

English Abstract

A control system for automatically achieving and maintaining a desired sodium/phosphate ratio and phosphate concentration of the boiler water in an industrial. boiler for minimizing corrosion. The system uses an adaptive controller that models the boiler which enables the system to predict boiler pH and phosphate concentrations at any future time given the feed rates, feed concentrations of high and low sodium/phosphate stocks, blowdown rate, mass of the boiler water, initial boiler phosphate concentration and initial pH. Once these future concentrations are determined, the controller then determines which feed rates will return the current boiler water state to, and then maintain the boiler water at, the desired sodium/phosphate congruency ratio in the least amount of time. Where the controller determines that it is not possible to move the current boiler water state to the desired congruency setpoint, the controller determines a feed rate program that will move the current boiler water state to the attainable steady state point closest to the desired setpoint. An alternative embodiment fixes the phosphate concentration by ratioing the phosphate feed with the blowdown rate while continuously monitoring blowdown pH. This arrangement allows for the control of sodium by switching between the high and low ratio sodium/phosphate stocks based on the measured pH with respect to the pH setpoint.


French Abstract

Système de commande pour l'obtention et le maintien automatique d'un rapport sodium/phosphate et d'une concentration en phosphate souhaités dans l'eau d'une chaudière industrielle afin de réduire la corrosion au minimum. Le système emploie un organe de commande adaptatif qui modélise la chaudière, ce qui permet au système de prévoir le pH de la chaudière et les concentrations en phosphate à un moment quelconque dans le futur étant donné les débits d'alimentation, les concentrations d'amenée de réserves de sodium/phosphate hautes et basses, le débit de détente, la masse de l'eau de la chaudière, la concentration initiale en phosphate et le pH initial de la chaudière. Une fois ces concentrations futures déterminées, l'organe de commande détermine ceux des débits d'alimentation qui ramèneront l'état actuel de l'eau de la chaudière, et qui le maintiendront, au rapport de congruence sodium/phosphate souhaité le plus rapidement. Lorsque l'organe de commande détermine qu'il n'est pas possible de faire passer l'état actuel de l'eau de la chaudière au point de consigne de congruence souhaité, l'organe de commande détermine un programme de débit d'alimentation qui permettra de faire passer l'état actuel de l'eau de la chaudière à un point stationnaire atteignable le plus proche du point de consigne souhaité. Une variante de mode de réalisation fixe la concentration en phosphate en établissant le rapport entre l'alimentation en phosphate et le débit de détente tout en surveillant en continu le pH de détente. Cet arrangement permet une régulation du sodium en alternant entre les réserves de sodium/phosphate hautes et basses en se fondant sur le pH mesuré par rapport au pH de consigne.

Claims

Note: Claims are shown in the official language in which they were submitted.





CLAIMS:

1. A method for controlling at least two interdependent
chemicals in the fluids of at least two continuously stirred
tank reactors (CSTRs) having respective blowdown flows and
steam rate flows defining respective cycles for each of said
CSTRs, said at least two interdependent chemicals being fed
to said fluids through a common feedwater to each of said at
least two CSTRs, said method comprising the steps of:
(a) establishing a respective mathematical model of each
of said CSTRs;
(b) monitoring the concentration of one of said at least
two interdependent chemicals in each of the fluids, the
temperature at which the pH is measured in each of the
fluids, the respective blowdown flow and the respective
steam rate flow;
(c) updating the models based on the concentration of
said one of said at least two interdependent chemicals in
each of the fluids, the pH of each of the fluids, the
temperature at which the pH is measured in each of the
fluids, the respective blowdown flow and the respective
steam rate flow;
(d) defining a respective target region of said at least
two interdependent chemicals in said respective continuously
stirred tank reactors and wherein said respective target
regions are scaled according to the respective cycles, said
respective target regions establishing a state of congruency
for each of the fluids;
(e) providing a feedstream of a high-pH fluid treatment
material comprising a mixture of said at least two
interdependent chemicals and a feedstream of a low-pH fluid
treatment material comprising a mixture of said at least two
interdependent chemicals for feeding, at respective feed
rates, to the fluids; and
(f) developing an optimum feed rate program for
controlling said feedstreams to automatically minimize the
time that said at least two interdependent chemicals in the



52


fluids spend outside of a common normalized target region
formed by the intersection of said respective target
regions.

2. The method of claim 1 wherein each of said fluids has
associated therewith a respective pumpable region in a CSTR
state space that defines all reachable concentrations of
said at least two interdependent chemicals in the respective
fluid and including therein said respective target region
for defining congruency of said at least two interdependent
chemicals in said respective pumpable region, said
respective pumpable region also being scaled according to
said respective cycles, said step of developing an optimum
feed rate program comprising overlaying said respective
pumpable regions and said respective target regions to
define a single pumpable region and to establish said common
normalized target region defined by the intersection of said
overlayed respective target regions.

3. The method of claim 2 wherein said fluids comprise
respective current concentrations of said at least two
interdependent chemicals and wherein said step of developing
an optimum feed rate program comprises the steps of:
(a) establishing a respective region formed by the
endpoint of all feed trajectories that move said respective
current concentrations of said fluids into said common
normalized target regions;
(b) selecting a new region formed by the the intersection of
said respective regions, said new region formed by the
intersection of said respective regions defining a common
normalized pumpable region.

4. The method of claim 3 wherein said common normalized
target region comprises vertices, including extreme
vertices, and edged and wherein said step of establishing a
respective region comprises forming a respective region
defined by feed trajectories that originate from said

53



current concentrations and intersect said extreme vertices
for each of said respective current concentrations said
fluids.

5. The method of claim 4 wherein said step of developing an
optimum feed rate program further comprises step of
evaluating, for each of said continuously stirred tank
reactors, sets of feed rate trajectories between the current
concentrations of said at least two interdependent chemicals
and said common normalized target region to determine the
time required to move said current concentrations into said
common normalized target region.

6. The method of claim 5 wherein said common normalized
pumpable region comprises vertises vertices and edges, together which
define a common normalized pumpable region perimeter, and
wherein said step of evaluating comprises the steps of:

(a) determining the time associated with driving the
current concentrations of said at least two interdependent
chemicals in each of said fluids along a first set of feed
rate trajectories formed between the current concentrations
and said vertices of said common normalized pumpable region;
(b) determining the time associated with driving the
current concentration of said at least two interdependent
chemicals in each of said fluids along a second set of feed
rate trajectories formed between said current trajectories
and said common normalized target region vertices, said
second set of feed rates being projected until they
intersect. said common normalized pumpable region perimeter,
if at all, to define a third set of feed rate trajectories;
and
(c) selecting one feed rate trajectory, from all of said
first and third sets of feed rate trajectories from each of
said fluids, that requires the least amount of time for at
least one fluid to reach an edge of said common normalized
target region.

54


7. The method of claim 6 wherein said feedstream of a
high-pH fluid treatment material comprising a mixture of
said at least two interdependent chemicals defines a first
feedstream and wherein said feedstream of a low-ph fluid
treatment material comprising a mixture of said at least two
interdependent chemicals defines a second feedstream and
wherein said selected one feed rate trajectory comprises an
endpoint and wherein said method further comprises the step
of determining how long to feed said first and second
feedstreams at said selected one feed rate trajectory, said
step of determining how long to feed said first and second
feedstream comprising the steps of:

(a) for each of said at least two CSTR fluids
establishing a respective feed rate trajectory defined by a
line from said respective current concentrations of said at
least two interdependent chemicals to the endpoint of said
selected one feed rate trajectory;
(b) for each of said at least two CSTR fluids,
determining the point at which said respective feed rate
trajectory interesects an edge of said common normalized
target region and determining the time associated with
moving said current concentrations to said point, said
intersected edge being a common normalized target region
edge different from said edge reached by said at least one
fluid in the least amount of time; and
(c) selecting the minimum of those times associated with
moving raid current concentrations to said point.

8. The method of claim 7 further comprising the step of
feeding said first and second feedstreams at said selected
one feed rate trajectory for an amount of time corresponding
to said selected minimum of those times associated with
moving said current concentrations to said point.

9. The method of claim 8 wherein each of said CSTR state
spaces are updated based on said feeding said first and
second feedstreams at said selected one teed rate trajectory

55


for said selected minimum of those times associated with
moving said current concentrations to said point.

10. The method of claim 9 further comprising the steps of:

(a) establishing a new common normalized target region
that is nested within said common normalized target region;
and
(b) repeating the steps of selecting one feed rate
trajectory that requires the least amount of time for at
1east one fluid to reach an edge of said new common
normalized target region; and
(c) repeating the steps for determining how long to feed
said first and second feeds-reams.

11. The method of claim 7 further comprising the step of
feeding said first and second feedstream at said selected
one feed rate trajectory for an amount of time corresponding
to the time it takes for new data to be available, said data
being defined as said concentration of said one of said at
least two interdependent chemicals in the fluids, said pH of
said fluids, said temperature at which the pH is measured,
said blowdown flow and said steam rate flow.

12. The method of claim 11 wherein each of said fluid state
spaces are updated based on said feeding said first and
second feedstream at said selected one feed rate trajectory
for said amount of time corresponding to the time it takes
for new data to be available.

13. The method of claim 11 wherein a new common normalized
target region is recomputed based on said new data.

14. The method of claim 2 wherein said feedstream of a
high-pH fluid treatment material comprising a mixture of
said at least two interdependent chemicals defines a first
feedstream and wherein said feedstream of a low-pH fluid
treatment material comprising a mixture of said at least two

56



56


interdependent chemicals defines a second feedstream and
wherein said fluids comprise respective current
concentrations of said at least two interdependent chemicals
and wherein said step of developing an optimum feed rate
program comprises the steps of:
(a) evaluating, for each of said CSTRs, gets of feed
rate trajectories between the current concentrations of said
at least two interdependent chemicals and said common
normalized target region to determine the time required to
move said current concentrations into said common normalized
target region;
(b) selecting that feed rate trajectory that moves at
least one fluid current concentrations into said common
normalized target region in the least amount of time; and
(c) feeding said first and second feedstreams at said
selected feed rate trajectory.

15. The method of claim 14 further comprising the steps of:

(a) establishing a new common normalized target region
that is nested within said common normalized target region;
and
(b) evaluating, for each of said CSTRs, sets of feed
rate trajectories between the current concentrations of said
at least two interdependent chemicals and said new common
normalized target region to determine the time required to
move said current concentrations into said new common
normalized target region;
(c) selecting a feed rate trajectory that moves at least
one fluid current concentration into said new common
normalized target region in the least amount of time; and
(d) feeding said first and second feedstreams at said
selected feed rate trajectory.

16. The method of claim 1 wherein each of said CSTRs is an
industrial boiler having a boiler fluid.

57



57


17. The method of claim 16 wherein said one of said at least
two interdependent chemicals is phosphate.

18. The method claim 17 wherein said one of said at least
two interdependent chemicals is sodium.

19. The method of claim 18 wherein said method further
includes the step of estimating the blowdown flow.

20. The method of claim 19 wherein said method further
includes the steps of calculating the phosphate
concentration and the sodium concentration in the boiler
fluid.

21. The method of claim 20 wherein said method further
includes the step of estimating a feedwater contaminant
ingress.

22. The method of claim 21 wherein said steps of estimating
a blowdown flow and a feedwater contaminant ingress are
based on a series of phosphate and pH measurements of the
boiler fluid wherein said method uses small sample
intervals.

23. The method of claim 1 wherein said method further
includes a step that accounts for dead time in each of the
continuously stirred tank reactors.

24. The method of claim 1 wherein said method further
comprises the step of controlling the blowdown flow of each
of said CSTRs.

25. An automatic control system for controlling at least two
interdependent chemicals in the fluids of at least two
continuously stirred tank reactors (CSTRs) linked in
parallel by a common feedwater line and wherein each CSTR
includes a respective blowdown flow and stream rate flow that

58



58


define respective cycles for each of said CSTRs and wherein
each CSTR has associated therewith a respective target
region of said at least two interdependent chemicals, said
respective target regions be ing scaled according to the
respective cycles of said CSTRs, said control system
comprising:
input means for receipt. of fluid parameters and control
means responsive to sai input means;
said control means using non-proportional control for
automatically minimizing the time that said at least two
interdependent chemicals in the fluids spend outside of a
common normalized target region formed by the intersection
of said respective target regions of said at least two
CSTRs;
wherein one of said fluid parameters comprises the pH of
the fluid and wherein said input means comprises means for
determining the pH value of each of the fluids; and
wherein said control means comprises a first feedstream
and a second feedstream for feeding first and second fluid
treatment materials, respectively, to the common feedwater
line at respectively determined feed rates, said first
material comprising a mixture of sodium and phosphate having
a first predetermined sodium-to-phosphate ratio and said
second material comprising a mixture of sodium and phosphate
having a second predetermined sodium-to-phosphate ratio.

26. The control system of claim 25 wherein said control
means further comprises an adaptive controller, said
adaptive controller modeling of each of said at least two
CSTRs.

27. The control system of claim 26 wherein said control
means further comprises monitoring means for monitoring the
concentration of said at least two interdependent chemicals
in said fluids, the temperature at which the pH is measured,
the blowdown flow and the steam rate flow, said monitoring
means being coupled to said adaptive controller in order to

59


update said model inch of each of said at least two CSTRs, the
concentration of said at least two interdependent chemicals
in said fluids, the temperature at which the pH is measured,
the blowdown flow and the steam rate flow being defined as
data.

28. The control system of claim 27 wherein said adaptive
controller generates said respective target regions in a
CSTR state space for each of said at least two CSTRs.

29. The control system of claim 28 wherein said adaptive
controller overlays said respective target regions to
generate said common normalized target region.

30. The control system of claim 29 wherein each fluid
comprises a current concentration for each of said at least
two interdependent chemicals and wherein said adaptive
controller analyzes all feed rate trajectories of said first
and second feedstreams that will drive said current
concentrations in each of said fluids from said current
concentrations to concentrations within said common
normalized target region in said CSTR state space, said
analyzation determining a respective region for each of said
fluids that is formed by the endpoints of all said feed rate
trajectories.

31. The control system of claim 30 wherein said adaptive
controller selects that region in said CSRT state space that
is formed by the intersection of said respective regions,
said selected region forming a common normalized pumpable
region that defines all reachable concentrations of said at
least two interdependent chemicals among said at least two
CSTR fluids, said common normalized pumpable region
comprising a first set of edges and a first set set of vertices.

32. The control system of claim 31 wherein said common
normalized target region comprises a second set of vertices



and second set of edges, said adaptive controller
determining for each of said CSTRs:

(a) the time associated with driving said current
concentrations of said at least two interdependent chemicals
along a first set of feed rate trajectories formed between
said current concentrations and said second set of vertices;
and
(b) the time associated with driving said current
concentrations of said at least two interdependent chemicals
along a second set of feed rate trajectories formed between
said current concentrations and said second set of vertices,
said second set of feed rate trajectories being projected
until they intersect said first set of edges of said common
normalized pumpable region to define a third set of feed
gate trajectories, said adaptive controller selecting one
feed rate trajectories that all of said first and third sets
of feed rate trajectories that requires the least amount of
time for at least one fluid to reach one of said second set
of edges.

33. The control system of claim 32 wherein said adaptive
controller calculates how long to feed said first and second
feedstreams at said selected feedrate trajectory, said
adaptive controller:

(a) establishing, for the other CSTRs of said at least
two CSTRs whose current concentrations have not reached one
of said second set of edges, a respective feedrate
trajectory between said current concentration and said
selected feedrate trajectory;
(b) said adaptive controller determining, for the other
CSTRs whose current concentrations have not reached one of
said second set of edges, the point at which each of said
respective feedrate trajectories intersects one of said
second set of edges and determining the time associated with
moving said current concentrations to said point, said
intersected edge being a common normalized target region
edge different from said edge of said common normalized

61


target region reached try said at least one CSTR fluid in the
least amount of time, said adaptive controller selecting the
minimum of those times associated with moving said current
concentrations to said point and feeding said first and
second feedstrearms at said selected feedrates for said
selected minimum time.

34. The control system of claim 33 wherein said monitoring
means updates said fluid state spaces for each of said CSTRs
based or said adaptive controller feeding said first and
second feedstreams at said selected feedrate trajectories
for said selected minimum time.

35. The control system of claim 34 wherein said adaptive
controller establishes a new common normalized target region
that is nested within said common normalized target region
and wherein said adaptive controller determines one feedrate
trajectory that requires the least amount of time for at
least one of said CSTRs to reach an edge of said new common
normalized target region and determines how long to feed
said first and second feedstreams at said one feedrate
trajectory.

36. The control system of claim 32 wherein said adaptive
controller feeds said first and second feedstreams at said
selected feedrates for an amount of time corresponding to
the time it takes for new data to be available.

37. The control system of claim 35 wherein said monitoring
means updates said CSTR state spaces for each of said CSTRs
based on said adaptive controller feeding said first and
second feedstreams at said selected feedrate trajectories
for said time it takes for new data to be available.

38. The control system of claim 37 wherein, said adaptive
controller recomputes a new common normalized target region
based on said new data.

62


39. A method for controlling the sodium-to-phosphate ratio
of the fluids of at least two continuously stirred tank
reactors (CSTRs) having respective blowdown flows and steam
rate flows defining respective cycles for each of said at
least two continuously stirred tank reactor, said fluids of
said at least two CSTRs being fed through a common feedwater
line, said method comprising the steps of:

(a) providing a supply of a first sodium phosphate fluid
treatment material to said common feedwater, said first
sodium phosphate fluid treatment material having a first
predetermined sodium-to-phosphate ratio and a first known
phosphate concentration;
(b) providing a supply of a second sodium phosphate
fluid treatment material to said common feedwater, said
second sodium phosphate fluid treatment material having a
second predetermined sodium-to-phosphate ratio and a second
known phosphate concentration;
(c) measuring a fluid parameter of each of said fluids
substantially continuously;
(d) determining the cycle of each of said at least two
CSTRs substantially continously;
(e) estimating the phosphate concentration in each of
said fluids;
(f) determining the effective sodium in each of said
fluids;
(g) determining the sodium-to-phosphate ratio in each of
said fluids and identifying a maximum sodium-to-phosphate
ratio and a minimum sodium-to-phosphate ratio among said
fluids, said minimum and maximum sodium-to-phosphate ratios
defining a first range having a first midpoint; and
(h) feeding said first sodium phosphate fluid treatment
material if said first midpoint is less than or equal to a
second midpoint of a predetermined ratio range of
sodium-to-phosphate, of feeding said second sodium phosphate
fluid treatment material if said first midpoint is greater
than said second midpoint.

63


40. The method of claim 39 wherein said step of determining
the cycle of each of said at least two CSTRs substantially
continuously comprises the steps of:

(a) measuring each of said blowdown flows substantially
continuously; and
(b) measuring the total feedwater flow substantially
continuously.

41. The method of claim 40 wherein said step of feeding said
first sodium phosphate fluid treatment material or said
second sodium phosphate fluid treatment material comprises
feeding said first or second sodium phosphate fluid
treatment material at a rate which maintains the respective
phosphate concentration of each boiler fluid between a
respective predetermined upper phosphate control limit and a
respective predetermined lower phosphate control limit.

42. The method of claim 41 wherein said respective
predetermined upper phosphate control limits are identical
and which form a first phosphate control limit, and wherein
said respective predetermined lower phosphate control limits
are identical and which form a second phosphate control
limit.

43. The method of claim 42 wherein said step of feeding said
first sodium phosphate fluid treatment material or said
second sodium phosphate fluid treatment material is fed at a
rate given by:

Image

where TotalFeedwater is said total feedwater flow;
FeedPO4 is said first known phosphate concentration or
said second known phosphate concentration, depending on
which fluid treatment material is being fed;
PO4Bound is said first phosphate control limit;

64


PO4Bound is said second phosphate control limit;
Cycles is said CSTR having a maximum cycle value
wherein said cycle is defined as:

Image

and Cycles is said CSTR having a minimum cycle value,
Steam (i) is the Steam flow rate for the "ith" boiler and
Blowdown (i) is the blowdown flow for the "ith" CSTR and
i=CSTR index for identifying a particular CSTR of said at
least two CSTRs.

44. The method of claim 43 wherein said step of feeding said
first sodium phosphate fluid treatment material or said
second sodium phosphate fluid treatment material at said
rate occurs as long as the following condition is met:

Cycles /Cycles PO4Bound /PO4Bound

65


45. The method of claim 41 wherein said step of feeding said
first sodium phosphate fluid treatment material or said
second sodium phosphate fluid treatment material is fed at a
rate given by:
FeedRate = .alpha. = maxi FeedRate min(i) + (1 - .alpha.)* min(FeedRate
min(i),
where 0 <= .alpha. <= 1;
i=
CSTR index for identifying a particular CSTR of said at least two CSTRs;
FeedRates min(i) =PO4Bound min(i)/ Cycle(i)*(TotalFeedWater)/FeedPO4;
FeedRate ~(i) = PO4Bound min(i) / Cycle(i)* (TotalFeedWater) /FeedPO4
TotalFeedwater is said total feedwaterflow;
FeedPO4 is said first known phosphate concentration or said second
known phosphate concentration, depending on which fluid treatment
material is being fed;
PO4Bound max(i) is said respective predetermined upper phosphate
control limit;
PO4Bound min(i) is said respective predetermined lower phosphate
control limit; and
Cycle(i) is defined as:

Image

46. The method of claim 45 wherein said step of feeding said
first sodium phosphate fluid treatment material or said
second sodium phosphate fluid treatment at said rate occurs
as long as the following condition is met:

max(PO4Bound min(i)/Cycle(i))~min(PO4Bound max(i)/
Cycle(i)).

47. The method of claim 46 wherein said step of determining
the effective sodium in each of said fluids comprises back
calculating sodium using a model projected phosphate
concentration given by;

66


PO4Est(i,r+dr)= Image PO4(i)+PO4Est(i,t)Image
where
PO4Est (i,t) is the estimated concentration in the fluid
at time t;
i=CSTR index for identifying a particular CSTR of said
at least two CSTRs;
PO4 (i) is the steady-state phosphate concentration;
T is the characteristic time of the fluid; and
at is the time between interval samples.
48. The method of claim 41 wherein said fluid parameter is
the pH of the fluid.

49. The method of claim 41 wherein said first known
phosphate concentration is identical to said second known
phosphate concentration.

50. The method or claim 39 wherein said method further
comprises the step of controlling the blowdown flow of each
of said CSTRs.

51. A system for simultaneously controlling respective
sodium-to-phosphate ratios of at least two boiler fluids of
respective industrial boilers that are fed through a common
feedwater, the industrial boilers having respective blowdown
flows and steam rate flows that define respective cycles for
each boiler fluid, said system comprising:
input means for receipt of a boiler fluid parameter for
each of the at least two boiler fluids and a parameter
indicative of the cycles of each of said industrial boilers;
and
control means responsive to said input means for
automatically driving the respective sodium-to-phosphate
ratios of said at least two boiler fluids to a desired

67


sodium-to-phosphate ratio region, said control means
comprising model phosphate projecting means for estimating
the sodium-to-phosphate ratios in each of said at least two
boiler fluids.

52. The system of claim 51 wherein said boiler fluid
parameter comprises the pH of the boiler fluid, the
respective pH of each of the boiler fluids being defined by
the respective sodium-to-phosphate ratio and the respective
cycles and wherein said input means comprises a respective
pH meter for determining the respective pH value of each of
the boiler fluids and providing the respective pH value to
said control means.

53. The system of claim 51 wherein said control means
comprises a first feedstream and a second feedstream for
feeding first and second fluid treatment materials,
respectively, to the common feedwater, said first material
comprising a mixture of sodium and phosphate having a first
material comprising a mixture of sodium and phosphate having
a second predetermined sodium-to-phosphate ratio and a
second predetermined concentration of phosphate.

54. The system of claim 53 wherein said first predetermined
concentration of phosphate and said second predetermined
concentration of phosphate are identical.

55. The system of claim 53 wherein said control means
comprises feeding means for feeding said first feedstream or
said second feedstream at a rate which maintains the
respective phosphate concentration of each boiler fluid
between a respective predetermined upper phosphate control
limit and a respective predetermined lower phosphate control
limit.

68




56. The system of claim 55 wherein said respective
predetermined upper phosphate control limits are identical,
referred to as a first phosphate control limit, and wherein
said respective predetermined lower phosphate control limits
are identical, referred to as a second phosphate control
limit.

57. The system of claim 56 wherein said control means
further comprises means for feeding said first feedstream or
said second feedstream at a rate given by:

Image

where
TotalFeedwater is the flow of said common feedwater;
FeedPO4 is said first predetermined phosphate
concentration or said second predetermined phosphate
concentration;
PO4Bound is a predetermined maximum phosphate
concentration;
PO4Boudmin is a predetermined minimum phosphate
concentration;
Cycles is said boiler having a maximum cycle value
wherein said cycle is defined as:

Image

and Cycles is said boiler having a minimum cycle value,
Steam (i) is the steam flow rate for the "ith" boiler and
Blowdown (i) is the blowdown flow for the "ith" boiler and
i=boiler index for identifying a particular boiler of said
at least two boilers.



69




58. The system of claim 57 wherein said feeding means
includes monitoring means that permits said feeding means to
feed at said rate whenever the following condition is met:

Cycles /Cycles PO4Bound/PO4Bound

59. The system of claim 58 wherein said monitoring means
alerts an operator if said condition is not met.

60. The system of claim 55 wherein said feeding means feeds
said first feedstream or said second feedstream at a rate
given by:

FeedRate min(i) = PO4Bound min/Cycle(i)*
(TotalFeedWater)/FeedPO4;
FeedRate max(i) = PO4Bound max(i)/Cycle(i)*
(TotalFeedWater)/FeedPO4
TotalFeedwater is the flow of said common feedwater;
FeedPO4 is said first predetermined phosphate concen-
tration, or said second predetermined phosphate
concentration, depending on which fluid treatment
material is being fed;
PO4Bound max(i) is said respective predetermined upper
phosphate control limit;
PO4Bound min(i) is said respective predetermined lower
phosphate control limit; and
Cycle(i) is defined as:

Image

61. The system of claim 60 wherein said feeding means
includes monitoring means that permits said feeding means to
feed at said rate whenever the following condition is met:
max(PO4Bound min(i)/Cycle(i)) min(PO4Bound min/Cycle(i)).

62. The system of claim 61 wherein said control means
further comprises means for estimating the phosphate
concentration in each of the boiler fluids.

70


63. The system of claim 62 wherein said control means
further comprises means for back-calculating the sodium
concentration in each of said boiler fluids.

64. The system of claim 63 wherein said means for
back-calculating the sodium concentration in each of said
boiler fluids uses the following model projected phosphate
concentration:

Image

where
PO4Est (i,t) is the estimated concentration in the
fluid at time t;
i=boiler index for identifying a particular boiler of
said at least two boilers;
PO4 (i) is the steady-state phosphate concentration;
T is the characteristic time of the fluid; and
dt is the time between interval samples.

65. The system of claim 62 wherein said control means
further comprises means for determining a
sodium-to-phosphate ratio for each of said boiler fluids and
for identifying a maximum sodium-to-phosphate ratio and a
minium sodium-to phosphate ratio from all of said boiler
fluids to define a first range having a first midpoint.

66. The system of claim 62 wherein said feeding means feeds
said first feedstream if said first midpoint is less than or
equal to a second midpoint of a predetermined ratio range of
sodium-to-phosphate, or feeding said second feedstream if
said first midpoint is greater than said second midpoint.

67. The system of claim 61 wherein said monitoring means
alerts an operator if said condition is not met.

71


68. The system of <claim 1 wherein said said system does not
control the blowdown flow of each of said boilers.

72

Description

Note: Descriptions are shown in the official language in which they were submitted.


CA 02415685 2003-02-04
'_~ai5 ~cy._ilca'~ic>rv ~~~ a ii~~~i.-.i~,rml ~pFO iw ~r ~ .,;i vF ~::c:-
iaer~d:r~a
.:3.~~p?i~at.:i~~r: ,,I=~:,~»C, f:~~_~-~-~ Alig~.;st. ;:~'l, ~~_:';,
r''~PPARATUS AND METHOD Ft)R. AL'TOMATICALhY ACHIEVING ATvdD
t'~AINTAIN:I~1G ~OP3~~RUFNT eONTT20I TN AN I?~1DI'STFIt,I~ BOILER
SI?ECIFICATIOP1
FIELD OF THE INVENTION
The invention pertains to automatic: control systems for
continuously stirred tank reactors (CSTRs). In particular, the
invention pertains to automatic control systems for achievirg and
maintaining optimum congruency and phosphate concentration required
to minimize corrosion in industrial high pressure boilers.
BACKGROUND OF TNVENTION
Industrial boilers heat up highly purified feedwater to
generate steam for power generation, heating, etc.
A natural consequence of steam production is the "cycling up"
in concentration of chemic:a:ls which enter the boiler inadvertently
(e. g., acid leaks) or intentionai.ly (e. g., corrosion inhibitors).
A small portion of the boiler water is "blown down" (i.e., removal
of concentrated boiler water from the boiler) to keep the
concentrations of non-volatile chemicals (i.e., chemicals that do
not flow out with the steam but rather remain substantially in the
boiler water) at acceptable levels. 'The rate of blowdown is
defined by the "cycles of concentration." Tha term "cycles of
concentration" is defined as the sum of the steam and blowdown
2

CA 02415685 2003-02-04
flowrates divided by the bl.owdown flowrate. Cycles in high
pressure boilers range from less than 10 to 100 or more. Thus a
chemical added at a low ce-ncentration (e.g., 0.5 ppm) in the
feedwater can cycle up to fairly high boiler concentrations (e. g.,
30 ppm) .
These boilers are susceptible to, among other things,
corrosion. To minimize corrosion, one basic type of corrosion
control program that is practiced in the United States within these
boilers is phosphate control programs. Typically, in phosphate
control programs, a sodium phosphate salt is fed into the solution
in order to buffer tr:e solution and to maintain that Ph with
sodium. The objective of these phosphate control programs is to
maintain the measured variables, phosphate and Ph, within certain
stated guidelines, which are dependent upon boiler' pressure by
controlling the sodium, phosphate and resultant Ph within the
boiler water. See "Sodium Phosphate Solutions at Boiler
Conditions: Solubility, Phase Equilibria, and Interactions with
Magnetite," by G. Economy, A.J. Fanson, Chia-tsun Liu, J.N.
Esposito, and W.T. Lindsay, Jr., Proc. Intl. Water Conf., 1975, pp.
161-173.
If the concentration of sodium within the boiler water (which
is given by the pH, i..e., pH is proportional to effective sodium)
is divided by the concentration of phosphate within the boiler
water, there exists a range of optimum sodium-to-phosphate (Na/PO')
ratios that, if achieved and maintained within the boiler water,
will minimize corrosion. Where the boiler water is operated and
3

CA 02415685 2003-02-04
maintained at a Na/P04 ratio that is below 3.0:1 , the boiler is
said to be operating with coordinated phosphate/pH control (also
known as "captive alkalinity'q) . Where the boiler water is operated
and maintained at a Na/P04 ratio that is between 2.2:1 and 2.8:1,
the boiler is said to be operating with congruent control. where
the boiler is water is operated at a Na/P04 ratio that is above
3.0, a boiler is said to be operating with "equilibrium phosphate
control." All. three types of confi rol can be attained and
maintained with the instant invention.
With any of these corrosion control programs, the boiler uses
phosphate as the major buffering agent. Additionally, sodium and
phosphate concentrations are interdependent variables that must
either be controlled simultaneously, or one subservient to the
other. They cannot be controlled independently.
Furthermore, boiler systems are extremely slow systems because
they comprise large volumes. As an example, a 280,000 pound water
boiler having a blowdown rate of :,000 pounds/hour takes over three
days to remove and replenish the boiler water. Many things can
happen during that time that can alter the operator's initial guess
at what concentrations should be added to manually correct control
problems.
The applicants have found i:.hat conventional control schemes
like Proportional Integral Derivative (PID) control are
insufficient to provide practical., universally applicable automatic
control of this boiler chemistry for a number of reasons. First,
setpoint overshoot is a problem when attempting to control pH in a
4

CA 02415685 2003-02-04
large volume system. Limitations in pumping capacity inherent in
a real-life pumping scheme make "integral windup" a serious
problem. Integral windup causes a control system to overshoot its
setpoint. Overshoot is also a problem in controlling pH with PID
control due to the asymmetric nature of pH control. Although this
problem could potentially be avoided using blawdown flow
controllers, these devices are expensive and difficult to maintain
and calibrate,
Second, tuning such a PID loop is very difficult. Although
tuning can be done in many ways, the methods generally require one
of two sets of conditions be maintained, either of which are
difficult to achieve in an c,perating boiler. In one general tuning
method, the boiler chemistry must be held constant for multiples of
the first order time constant defined by the volume of the boiler
divided by its blowdown flow rate. In real-life applications, such
a steady-state cannot be established far that length of time due to
small perturbations in feedwater contaminants concentrations. In
the other general tuning method, the boiler chemistry must be
driven out of the region normally considered to be non-corrosive to
derive the tuning constants. This negates the beneficial effect of
the treatment. Since any c:~ange in blowdown flow rate (a normal
part of boiler operations; will render the measured tuning constant
invalid, tuning must be repeated for each blowdown flow setting.
There is one reference to sadium/phosphate control in the
literature which demonstrates the difficulties of this method and
its shortcomings. In "A Practical Approach to Real Time Data

CA 02415685 2003-02-04
Acquisition and Automated chemical Feed at a Fossil Fueled Cycling
Duty Station", by C.E. Frederick presented at the Internationa~
Conference on Cycle Chemistry in Fossil Plants, June 4-6. 1991, the
boiler system was tuned using a semi-empirical method to a specific
boiler, rather than being adaptable to various types and sizes of
industrial boilers. Furthermore, the system disclosed in that
reference requires the use of phosphate analyzers which are
expensive and require frequent re-~~alibratiens and maintenance.
The closest art to automatically controlling the Na/P04 ratio
in the water of an industrial boiler is in automated pH Control
systems. The control of pH is in itself a difficult ta.-:k, as
discussed in U.S. Patent No. 5,132,916 (Gulaian et al.).
The following U.S. Patents disclose examples of automated pH
control systems: 4,053,74:3 (Niemi), 4,239,493 (Niemi et al.),
4,181,951 (Boeke), 5,132,916 (Gulaian), 5,262,963 (Stana),
4,016,079 (Severin), 5,248,577 (Jerome), 4,033,871 (Wall) and
5,057,229 (Schulenberg).
The Niemi patent discloses an automatic system for controlling
the pH and other concentration variables in a chemical reactor.
However, use of that system would not be adaptable to an industrial
boiler for the following reasons. The system utilizes a method
that requires a steady state that is reached rapidly, which, as
discussed previously, an ~nduGtrial boiler does not exhibit.
Consequently, the Niemi patent teaches controlling pH by use of a
PID controller, which, as discussed previously, would be difficult
6

CA 02415685 2003-02-04
to use in Corrosion Control phosphate (CCn) programs described
above.
The Niemi et al, patent discloses an automatic system for
controlling the pH in a continuous flaw vessel. However, this
system is also not adaptable to industrial boilers for the
following reasons. For boiler systems, the known tuning methods do
not apply for the .reasons described above. If the residence time
distribution is known, then simulation of tuning methods requires
a perfect match of a simulator and reality. The assumptions of
linear processes of first order reactions is not applicable.
Therefore, the method listed in Niemi et al, will only work for
systems with small perturbations. industrial boilers exhibit
larger deviations. Furthermore, Niemi et al. identifies
proportional, proportional-integral and proportional-integral-
derivative controls along with an adjustable gain controller.
Limitations on feed concentrations versus system volume will make
any adjustable gain ineffective when bounded by limitations in a
"pumpable region." Finally, the same pumpable limitations will
make integral windup a sex-ious problem in a large volume system.
The Boeke patent discloses an automatic control system for the
adjustment of pH that ~s desc:ribed using the term "on-off".
However, this is not an ON,iOFF controller. The series of solenoids
that actuate flow across different size orifices produce a signal
proportional to feedback. The series of solenoids provides
proportional response that is discreet within a specific flow
7

CA 02415685 2003-02-04
window. This is analogous to a stepwise integration of a
continuous function.
The Gulaian patent discloses an automatic system for
controlling pH and utilizing an estimation for a pH titration curve
in the adaptive control of pH. However, this system is also
relegated to short residence times and the use of proportional-
integral control. Furthermore, the patent does not discuss
limitations from integral windup.
The Stana patent discloses an automatic system for controlling
a phosphoric acid plant. However, this system does not involve a
model of the system but rather teaches a target feed where the
system is compensated for its chemical deficit and then placed in
steady state. The algorithm utilized by the system contains
predetermined constants that are unique to a particular phosphoric
acid plant, and are therefore, not readily adaptable to a variety
of phosphoric acid plants (e. g., different plant volumes would
require that new constants be calculated and inserted into the
algorithm). Moreover, this system controls only sulfuric acid feed
and does not try to control two interdependent variables.
The Severin patent discloses an automatic chlorine and pH
control apparatus for swimming pc:ols. The apparatus controls two
variables, i.e. , chlorine and pH, under the assumption that the two
are not interrelated. A1'~hough ~.hlorine affects pH, chlorine has
a minor effect on pH and can be isolated and controlled separately.
This is because in a swvimming pool, chlorine is not the only
buffering agent. Its contribution to the pH is masked by the high
8

CA 02415685 2003-02-04
concentration of anions from the makeup water and atmosphere. This
allows the pH to be controlled independent of the chlorine
concentration. In contrast, as discussed earlier, a congruent
controlled boiler uses phosphate as the major buffering agent, and
the pH and phosphate are interdependent variables that must either
be controlled simultaneously, or one subservient to the other.
They cannot be controlled independently. In addition, the Severin
apparatus also ignores the cycle time of a swimming pool and
assumes that the control is constant through the system. It goes
not account for lag and residence time effects and probably cycles
up and down drastically when in operation. Finally, the pH control
range is anticipated as narrow, anc3 works on the assumption that pH
is linear in the chosen range.
The Wall patent discloses a system for continuously monitoring
and controlling the pH and free halogen in swimming pool water.
Although this patent mentions the concept of two-sided control
(i.e., monitoring whether pH or halogen or both fall within or
without predetermined ranges), the control of the pH of swimming
pools and the control of pH in a boiler are not interchangeable, as
described above.
The Schulenberg patent discloses an automatic system treatment
of cooling circuit water. Although this system describes on/off pH
control of a single component to provide one sided control and the
system adds other components according to vaporous loss, the
chemistry is different fro.~n that of a b oiler. The chlorine in the
Schulenberg patent is not the major buffer, and no attempt is made
9

CA 02415685 2003-02-04
to maintain the COZ alkalinity. In addition, this system cannot
control two interdependent variables. The corrosion inhibitor and
the pH are not interdependent as are the phosphate (similar to a
corrosion inhibitor) and pH..
The Jerome patent discloses a reactant concentration control
method and apparatus for precipitation reactions. The system does
base feed one reagent and adjusts the second. However, the method
and apparatus assume that the system is near steady state at all
times. The calculations are:Lineari.zed and performed incrementally
to make the calculations simpler. The model used in the
method/apparztus is not a true continuously stirred tank r.~sctor
(as is the model for industrial boilers).
U.S. Patent No. 5,11,716 (Muccitelli), which is owned by the
same Assignee of the present patent application discloses a method
of reducing corrosion in a boiler using coordinated phosphate
control. However, this method calls for the administering of
particular hydroxyethyl piperazines in specific ratios with
phosphate, i.e., there is no automatic apparatus nor methods
disclosed of canducting this feed.
Two other references which discuss coordinated phosphate
control axe: Justification and Engineering Desiqn for the On-Line
Monitorind,and Automation,_of a Congruent Fhosphate/pH Program, by
Michael E. Rogers, Ian 'Jernappen and Stephen Porter, Paper No. 413,
The NACE Annual Conference and Corrosion Show 1992;. Expert System
Helps Fine-Tune Boiler-Water Chernistr , by Leyon O. Bretsel and Lon
C. Brouse, Power Magazine 1987. In the former reference, although

CA 02415685 2003-02-04
a proposal is discussed for controlling phosphate feed to the
feedwater while controlling conductivity in the boiler water, there
is no disclosure of any automatic simultaneous control of phosphate
and congruency (Na/P04 ratio). With regard to the latter reference,
although there is a discussion of providing the operator with
chemical feed adjustments, there is no real-time, automatic control
system that is disclosed for controlling the chemical pumps in
order to control congruencyN
Therefore the prior art does not disclose an effective method
for controlling two interdependent and non-volatile chemicals,
e.g., sodium and phosphate, in a system that is rarely at steady
state that is auto-tuning and does not require control of the
blowdown flow. None of the above cited art have devised an
apparatus nor a method for achieving an automatic: coordinated
sodium/phosphate control system for a variety of industrial boilers
without the need far on-line phosphate analyzers.
OBJECTS OF TFiE INVENTION
Accordingly, it is the general abject of this invention to
provide an automatic system for coordinated, equilibrium and
congruent sodium/phosphate control of an industrial boiler which
improves upon and overcomes the disadvantages of the prior art.
It is another object of this invention to provide an automatic
system for sodium/phospY~ate c:;ntral that requires no tuning
procedure.
It is still anotY~er object of the preferred embodiment of this
invention to provide an automatic system for sodium/phosphate
11

CA 02415685 2003-02-04
control that can be implemented universally, that is easily adapted
to any particular boiler system.
It is still yet a further object of this invention to provide
an automatic system for sodium/phosphate control where feed pumps
are the only means available for control and, in particular, where
blowdown controllers tied to the automatic system are not
available.
It S.s yet another obje<.a of this invention to function as an
advisory system, instructing the operator what to do, or to
directly control congruency.
It is still yet another object of this invention to control
any number of chemicals used in controlling congruency, e.g.,
polymer or chelant feeds as wel= as sodium and phosphate.
It is still yet a further object of this invention to control
chemical concentrations when precipitation or volatilization is
occurring.
It is still even a further object of this invention to control
more than one boiler system simultaneously.
It is another object ~.af this invention to provide an efficient
method to achieve the congruency control (targets region while
minimizing the time spent outside of this control region.
It is another objective of this invention to, once within the
target region, provide an efficient method to achieve the congruent
control ratio and phosprat2 setpoints and to remain within the
target region.
12

CA 02415685 2003-02-04
It is still another object of this invention to provide an
information database of the congruency control system for use in
diagnostics.
It is yet a further objects of this invention to provide an
alternative means of determining blowdown without having to utilize
expensive blowdown measurement equipment.
It is still yet a further object of this invention to provide
an alternative means of determining the contribution to boiler
water pH from ionic feedwater contaminant ingresses without having
to use chemical analyzers and feedwater flow meters.
It is still a further object of this invention to provide a
controller having a well-defined response for those situations
where controllers using conventional general purpose equation
solvers would simply Conclude that there is no possible response.
It is further object :~f this invention to provide a chemical
feed system which minimizes dead time while maximizing
controllability.
It is yet another object of a second embodiment of this
invention to provide an automa tic congruent control system that
utilizes a pumping scheme which fixes the phosphate concentration
in the boiler water while permitting the sodium/phosphate ratio to
be controlled.
SLJMMt~RY OF 'I HE INtiENTION
These and other obje~:ts of the instant invention are achieved
by providing an automaticv control system for controlling at least
two interdependent chemicals in the fluid of a continuously stirred
13

CA 02415685 2003-02-04
tank reactor system having an effluent flow. The control system
comprises input means for receipt of fluid parameters and control
means responsive to the input means. The control means uses non-
proportional control for ,:automatically achieving and maintaining a
setpoint of the at least two W terdependent chemicals in the fluid
without controlling the effluent flow.
In addition, a second embodiment is provided for controlling
the sodium-to-phosphate ratio <ind the phosphate concentration o.f a
boiler fluid in an industrial boiler having a blowdown flow. The
system comprises input means for receipt of a boiler fluid
parameter any? a parameter zndicat:ive of the phosphate conc~.ntra-
tion. The system further comprises control means responsive to the
input means for automatically achieving and maintaining a
predetermined fixed phosphate concentration of the boiler fluid and
for automatically achieving and maintaining a predetermined desired
sodium-to-phosphate ratio of the boiler fluid without controlling
the blowdown flow.
DESCRIPTION OF THE DRAWINGS
Other objects and many of the attendant advantages of this
invention will be readily appreciated as the same becomes better
understood by reference to the following detailed description when
considered in connection °.~ith the accompanying drawings wherein:
Fig. 1 is a block c;iagz-am of the Model Adaptive Corrosion
Control (MACC) system;
Fig. 2 is a block diagram of the input/output of the MACC
controller;
14

CA 02415685 2003-02-04
Fig. 3 is a diagram showing the boiler water
influents/effluents used by she MACC controller boiler model;
Fig. 4 is a time line diagram of the MACC feed program;
Fig. 5 is a diagram of the congruency line and target region;
Fig. 5 is the boiler state space diagram;
Fig. 7 is the boiler state space diagram depicting various
feed programs;
Fig. 8 is a time domain diagram depicting the minimization of
the first stage period.
Fig. 9 is a diagram depicting two feed rate trajectories
having similar time periads within the boiler state space diagram;
Fig. 10 is the boiler state space diagram depicting the
shortest time feed programs;
Fig. 10A is a diagram depicting a portion of the candidate
shortest-time feed rate trajectories;
Fig. lOB is a diagram depicting the remainder of the candidate
shortest-time feed rate trajectories;
Fig. 11 is the boiler state space diagram for stage 2 and
stage 3 of the feed program;
Fig. 12 is a blacl~ diagram of an alternative embodiment, the
ON/OFF control system; and
Fig. 13 is a diagram depicting the ON/OFF control system
operation around the pH setpoint at a fixed phosphate
concentration.

CA 02415685 2003-02-04
DETAILED DESCRIPTION OF THE PREFERRED EMBODIMENT
Unless otherwise specified, all references to sodium or
sodium-to-phosphate ratio (Na/P04) refer to that sodium which
interacts with the phosphate to maintain the. boiler water so as to
inhibit corrosion. This is also referred to as "effective sodium"
or the "effective sodium-to-phosphate ratio."
Referring now in detail to the various figures of the drawing
wherein like reference characters refer to like parts, there is
shown at 20 in Fig. l, the preferred embodiment of the model
adaptive congruent controller system 20 of the present invention
(hereinafter known as the MACC system) . Generally, the MACC system
20 uses a model of an industrial bailer to predict the feed rates
of particular mixtures cf two chemicals, e.g., sodium and
phosphate, to achieve and maintain an acceptable range of sodium-
to-phosphate congruencies and phosphate concentrations where these
congruencies are defined by the sodium-to-phosphate ratio being
between high and low limits defined by the boiler operating
conditions, and then cheeks itself against its prediction and
adapts its model to impz~ove its control. The check is provided for
by a laboratory pH and POr analysis, rather than with the use of
any on-line pH analyzers or P~~4 analyzers. Hereinafter, the
targeted range of congruency and phosphate is referred to as the
target region and the specific congruency and phosphate desired is
referred to as the setpo.int, as Bill be discussed in detail later.
The MACC system 20 i~ arranged to control water treatment
chemicals to be introduced into an industrial boiler 22 by way c;f
16

CA 02415685 2003-02-04
the feedwater 24 to the boiler 22. The boiler 22 has an effluent
flow (hereinafter known as blowdown flow 26) and a blowdown valve
28. The MACC system 20 does not control thF~ blowdown flow 26 via
the blowdown valve 28. In fact, one of the distinguishing features
of the MACC system 20 over conventional boiler fluid control
systems is that the blowdown flow z6 varies independently of the
MACC system 20.
The MACC system 20 basically comprises a first chemical
feedstream 30A, a second feedstream 308, a ~~ontrol means 32 and an
input means 34. The first chemical feedstream 30A and the second
chemical feedstream 308 are each connected to the feedwater line
24. The first chemical feedstream 30A is arranged to deliver a
first fluid treatment material or chemical, e.g., sodium phosphate
mixture having a particular sodium-to-phosphate ratio, to the water
in the boiler 22. Similarly, the second chemical feedstream 30B is
arranged to deliver a second fluid treatment material or chemical,
e.g., a sodium phosphate mixture having another particular sodium-
to-phosphate ratio, to the water in the boiler 22. It is important
to note at this juncture tt:at the sodium-to-phosphate ratio in the
first chemical feedstream BOA must be different from the sodium-to-
phosphate ratio in the second chemical feedstream 30B. The
particular sodium-to-phosphate ratio (Na/P04) determines a
particular pH (high or low) for 'that flrid treatment material.
Sodium and phosphate are non-volatile chemicals in that they
do not flow out with the steam but rather only leave the boiler
water through the blowdown flow. Furthermore, these chemicals are
17

CA 02415685 2003-02-04
interdependent in that they both must be controlled in order to
achieve and maintain the desired sodium-to-phosphate ratio in the
boiler water.
Each feedstream 30A and 30B includes electrically driven pumps
34A and 34B which are coupled to the outlet of respective chemical
feed tanks 36A and 36B, via respective draw down assemblies 38A and
38B. The chemical feed tanks 36A and 36B contain high-pH and low-
pH sodium phosphate fluid treatment materials, respectively. The
draw down assemblies 38A and 38B act as accumulators for enabling
precise control of the pumps 34A and 34B by the control means 32.
The control means 32 i.s arranged to precisely control the two
chemical feedstreams 30A and 30B by controlling the operation of
the pumps 34A and 34B i.n order to achieve and maintain a
predetermined desired sodium-to-ahosphate ratio in the boiler
water.
The control means 32 basically comprises a computer-based
control unit 42 such as that provided by Betz Laboratories, Inc.
under the mark SMARTSCAN PLus and associated software/firmware 44
and a monitorjkeyboard 46. The MACC system software 48 for
effecting the operation o~ the control means 32 is set forth in
Appendix A, along with an intev-face portion 45t the interface
portion 45 interfaces the :>~LARTSCAPd Plus software/firmware 44 with
the I~iACC system software a8. fhe control means 32 also includes a
feed pump controller 50 am the associated draw down assemblies 38A
and 38B.
18

CA 02415685 2003-02-04
The feed pump controller 5c~ ~3nd the associated draw down
assemblies 38A and 38H are constructed in accordance with the
teachings of U.S. Patent tJo. 4,897,797, assigned to the same
assignee as this invention, namely Betz Laboratories, Inc., and
whose disclosure is incorporated by reference herein.
As will be discussed in detail below, the feed pump controller
50 receives the optimum feed rates for both pumps 34A and 34B from
the M.ACC software 48 via the computer 42.
The input means 40 basically comprises a lab analysis of a
sample from the blowdown flow 26. The lab analysis provides, among
other boiler water parameters, measured pH and measured PO' values
which are then manually entered into the MACC software 48 via the
computer 42.
The controller 52 of the MACC system 20 resides in the MACC
software 48. As shown in Fig. 2, the controller a2 takes the
external inputs of time, pH, Pn~ concentration and temperature,
which are all measured using samples taken from the boiler blowdown
line 26. The MACC system 20 is designed to perform well using only
the once a day or once a shift sampling rates typical of manual
congruent control. The operator makes these measurements on a
blowdown sample, and then enters the results into the controller
52. By contrast, conventional control algorithms, such as PID,
would require the much shorter- sampling intervals typically
associated with on-line pH and phosphate measurements. The
controller 52 then utilizing the model equations, discussed below,
provides the external outputs of the feed rate (fa) of chemical
19

CA 02415685 2003-02-04
pump 34A and the feed rate ,fb) of chemical pump 34B to the feed
pump controller 50 for precise:Ly controlling pumps 34A an3 34B at
their respective feed rates. These contraller 52-determined-feed
rates are also fed back .xs input to the controller 52, for
calculating future feed rates. A third external output, a status
code (e. g., for setting alarms, error reporting, etc.) is included
in the MACC software 48.
As mentioned earlier., the controller 52 contains model
equations tr3t enable the MACC. system 20 to predict boiler pH and
phosphate concentration at any future time, t, given the feed
rates, feed concentrations, blowdown flow rate, mass of the boiler
water, initial boiler phosphate concentration and initial pH..
The MACC controller 52 uses these equations, as will be
discussed in more detail below, by first running the model
equations backward for parameter estimation (i.e., to update the
boiler model) and then running the equations forward to determine
the optimal feed program.
Since future concentrations of Na and P04 depend upon blowdown
rate, the model equations are used along with the previous and
current phosphate samples, elapsed time between samples, and feed
rates over the period t.a back-calculate the blowdown rate, B,
required to account for the change in boiler phosphate
concentration observed. This eliminates the need fer a direct
blowdown flow measurement apparatus which is typically a costly
device. Similarly, the last twa pH samples, elapsed time between
samples, feed rates and t~.ne estimated blowdown rate (B) are used,

CA 02415685 2003-02-04
in conjunction with the model., to back-calculate a "feedwater
contaminant ingress (FCI)"', L, into the boiler. This FCI
represents a generic acid/base flow into the boiler that accounts
for the difference between the pH that should be in the boiler,
according to the model, and the measured pH. This flow represents
the sum of sodium ions, and other positively charged ions, and
negatively charged ions in the feedwater 24 (Fig. 1). Thus, it is
possible that the sum of these ions can have a positive or negative
sign associated with it. again, this eliminates the need for a
chemical analyzer and flow meter in the boiler feedwater stream.
Once the blowdown rate, B, and the feedwater contaminant
ingress, L, are estimated, the madel equations are run.forward. In
principle, all possible feed programs can be plugged into the model
and the future boiler sodiurn/phosphate ratio and phosphate
concentration predicted by the model compared with their control
ranges and setpoints. A~= described below, more efficient and
robust methods of determining this optimal feed program are
employed. The optimal feed program, which will remain in effect
until the next operator sample is entered, is the one that most
rapidly brings the boiler Pta/PO' ratio and phosphate concentration
into their control limits (i.e.,. the target region, as will be
discussed later) , and ono.e ~.~ithin the control limits, to the
setpoint.
If a situation arose Juch that the chemical feed pumps 34A and
s4B did not have the required pumping capacity to counteract an
unusually large FCI , it ~;ould be impossible to bring the system
:' 1

CA 02415685 2003-02-04
into the control range. In that situation, the operator would be
informed of the problem and the MACC controller 52 would deliver as
much treatment as it could to counteract the FCI. In general,
whenever a situation arises where the MACC controller 52 determines
that the target region 60 (or setpoint 58, as will be described
Later) cannot be reached, the MACC controller 52 sets the feed
rates, fa and fn, so that the distance between the model predicted
steady-state boiler Na and PO4 concentrations and the target region
60 (or setpoint 58} is minimizQd.
The model equations are based on the fact that the MACC
controller 52 assumes that only :a odium and phosphate are prey ent in
the boiler 22 (Fig. 3), Since both sodium and phosphate are non-
volatile, the boiler steaming rate does not impact upon sodium or
phosphate mass balance around the boiler 22, and thus does not
appear in Fig. 3. The FCI, L, is idealized as a constant moles/hr
flow rate into the boiler 22. The mass, M, of the boiler is
assumed constant. Furthermore, hereinafter use of the term "P04"
refers to total phosphate concentration, i.e., the sum of [H3P04]
phosphoric acid, and the charged forms of P04: [HZP04~ ] monobasic
phosphate, [HPO4-] dibasic phosphate and [P04~] tribasic phosphate.
To that end and based on the assumption that the boiler water
concentrations are uniform (good mixing), the first model equation
is given as:
M*d(P04)/dt .= fa*P04a + fb*P04b w P04*B (Equation #1}
where,
~P04=boiler (hence blowdown) phosphate concentration, moles/kg;
~fa=feed rate (kg/hr) for chemical pump ~34A;

CA 02415685 2003-02-04
~fb=feed rate (kg/hr) for chemical pump 34B;
~P048=feed concentration (moles/kg) for phosphate from tank 36A;
~P04e feed concentration (moles/kg) for phosphate from tank 36B;
~blowdown rate, in kgs/hr, B; and
~mass of boiler, in kgs, M.
Equation #1 states that the rate of change of the total phosphate
in the boiler 22 equals the net flow rate of phosphate into/out of
the boiler 22.
Solution of this first order differential equation under the
assumption that M, fa, P04a, fb, P04h and B remain fixed, yields the
following chemical concentration functions:
PO'(t) - ((fg*P04a + fb*P04b),''B)*(1-e~~tr,~) + P04(0)*e~-t,~r~
(Equation #2)
Na(t) - ( (fe*Naa + fb*Nab +L} j B) * (1-ewc~'~) + Na(0) *e~-c~'~
(Equation #3)
where
~r = first order time constant = M/B;
~Naa= feed concentration (molen/!~g) for sodium from tank 36A;
~Nab= feed concentration (moles/kg) for sodium from tank 36B;
~L= FCI (moles/hr) into boiler 22;
~P04(0)=initial phosphate concentration (moles/kg); and
~Na(0)=initial sodium conrentratian.
Equation #3 follows by analogy with Equation #2 since the sodium
and phosphate flows into/out of the boiler 22 are exactly
analogous, except for the addit~_on of the FCI, L, (also assumed
constant) into the boiler 22.
It is apparent that unlike phosphate, which is directly
measured by the operator, ~he sodium concentration is not directly
measured. Therefore, the sodium concen'~ration must be computed
indirectly using measured pEi corrected to 25°C and measured PO'.
Using the previous assumption that only sodium and. phosphate are
active in determining congruency, the sodium concentration, Na, can
23

CA 02415685 2003-02-04
be computed, assuming elect~oneutrality in the boiler watar, using
a charge balance equation.
The MACC controller 52 feed program consists of three stages,
as shown in Fig. 4. During the first stage, chemical pumps 34A and
34B are held at the constant feed rates, fag, fb~ for a length of
time, dt~ so as to bring the Na/P04 ratio and phosphate within their
control limits. Similarly, the pumps 34A and 34B are held at faz,
fez for time interval, dtz, in order to bring the Na/P04 ratio and
phosphate to their setpoints. Finally, once these setpoints ara
reached, the steady state feed rates, fa3; f~ are used to maintain
them. Each time a new pH, P04 measurement is entered by the
operator, the feed program calculates these eight parameters (dt~,
dtz, fe~, fb~, faz, fbz, fa3, and f~) , as will be described later.
The goal of the MACC system 20 is to choose the optimal feed
program. As shown in Fig. 5, the optimal feed program can be
defined as:
1) bringing the current boiler water Na/P04 ratio and
phosphate concentrations (point 54) within their control
limits as soon as possible (i.e., minimizing dt~).
2 ) once within these control limits, bringing the Na/P04 ratio
and the phosphate tc th~a setpoint as soon as possible
(minimizing dtz~.
Chemically correct Vongruent control is obtained by choosing
a narrow control range around the setpoint for Na/PO~ ratio, and
choosing as wide a contrcl range as acceptable for phosphate. This
allows the algorithm to focus on getting the critical Na/P04
24

CA 02415685 2003-02-04
ratio correct initially, except when phosphate levels are so far
off that they present an equally critical control problem. For
this reason, by default, t he MACC controller 52 automatically
defines a narrow control range around the congruency setpoint, the
width of which reflects the Na/P04 ratio variation associated with
instrumental and operatir~nal pH variability. In particular, there
is shown in Fig. 5, the desired cor~gru~ncy line 56 for sodium and
phosphate and the ideal congruency setpoint 58. The pH and
phosphate control limits define a target region 60 around the
setpoint 58.
The first step in determining the optimal feed program is to
estimate the blowdown flow rate, B, and the feedwater contaminant
ingress, L. As shown in Fig. 4, the MACC controller 52 stores the
previous as well as the currently entered P04 and pH entries, which
are referred to as "lastP04, lastpH, P04 and pH", respectively.
Equation #2 is used along with the known feed program between
samples (i.e. , the previously estimated f,j~, fb~, etc. ) to back-
calculate the blowdown flaw rate, B. A blowdown flow rate is
estimated and then Equation #2 is applied to each of the three feed
stages in turn in order to predict what the concentration would
have been had the estimated blowdown flow rate been the actual
blowdown flow rate. In particular, Equation #2 is sequentially
solved as follows:
P041=( (fad*P04a+fb~*P04b),~B) * r, 1-2~-~co,~)+laStPO4*e~'dc»T>
(Equation 2A)
P042=( (fat*P04a+fbz*P04b) /B) *', I-e~'dt2;'>)+PU41*et'dt2~,)
(Equation 2B)

CA 02415685 2003-02-04
P043=( (f~*P048+f~*P04b)i'B) * ( l-e~'dr3/ra)+P042*e~-dc3/r>
(Equation 2C)
wherein the concentration at the end of stage I is substituted for
the initial concentration at the beginning of stage 2, etc.
If the estimated blowdown flaw rate is exactly right, then
P043 (the predicted concentration a t the end of stage 3) will be
equal to P04, the measured phosphate in the boiler. If P043 is
less than P04, the estimated blowdown flow rate, B, must be too
high (i.e., it blows out too much phosphate). In other words, the
greater the :lowdown estimate, the smaller the predicted phcphate
concentration. This uniform P04 vs. blowdown response implies that
a particularly simple and reliable method, known as bisection, can
be used to find the blowdown estimate that is exactly correct.
As stated earlier, assuming e~lectr~neutrality of the boiler
water, the current and preceding phi and Po4 measurements are used
in a charge balance equation to calculate Na concentrations, in
particular, "lastNa and Na". In a manner. exactly analogous to the
manner in which the blowdown was determined via phosphate, the
feedwater contaminant :ingre:~s, L, is determined using Equation #3
sequentially and by comparing the Na prec?icted using an estimated
L with actual Na estimated from pH and P04.
The root finding process described above can fail in three
ways. First, it may turn out that nc re<3souable blowdown flow rate
is consistent with the starting and ending phosphate measurements.
For example, if the blowdown valve 28 were completely turned off
(B=0), the predicted phosptuate at the end of stage 3 would be at
its maximum. A P04 entry greater than this maximum would be
26

CA 02415685 2003-02-04
inconsistent with the model, since no possible adjustment of the
blowdown could match the given data. Similarly, if successive pH
entries that lead to outrageously large (physically impossible)
FCIs (e.g,. due to measurement error, data entry errors by the
operator, erroneous model parameters, etc.), the MACC controller 52
sets particular error flags (E._INCONSISTENT_P04 or
E_INCONSISTENT_PH flags) i.n the MACCr software 48 and then continues
to use the previously estimated feed program.
The third way that the root finding can fail is that
subjecting lastpH, iastP04, pH, and P04 to small changes consistent
with their variability results in relatively large changes in the
estimated steady state boiler concentrations. The user can specify
both the expected variabil:iøy in t:he inputs, and the acceptable
percentage variation in the steady-state concentrations induced by
this variability. If the expected variations in pH and phosphate
lead to unacceptably large variations in estimated steady-state
boiler concentrations, the E~INDETERMINA'rE flag is raised, and MACC
controller 52 continues tc use t=he previously estimated feed
program. A common situation that raises the E-INDETERMINATE flag is
when the interval between samples is much smaller than the boiler
residence time.
Given any feed program (dt~, dt~, dt.3, f~~, fb~, fat, f~,. fa3, and
fb3), blowdawn (B) and feedwater contaminant ingress (L), and the
initial boiler phosphate concentration and pH, the Ea_uations #2, #3
and the charge balance equa~::ion, c-~n be used to predict Na and P04
concentrations for any time. In principle, all possible feed
27

CA 02415685 2003-02-04
programs could be calculated and the one feed program that brought
the boiler sodium and phosphate concentrations within the target
region 60 (and, secondarily, to the setpoint 58) as quickly as
possible, would be selected. However, such a brute force approach
is impractical if riot impossible. On the other hand, the MACC
controller 52 eliminates the need to use such a brute force method
by way of a series of easily solved geometrical problems using a
boiler state space diagram, as discussed below.
In its two component forms, the boiler state space (Fig. 6) is
visualized as a simple X-Y graph with PO~ (boiler phosphate
concentration) on the X axis and Na (boiler sodium concentration)
on the Y axis. It is called a state space diagram because each
possible state of the model boiler corresponds to a point in the X-
Y "space". For any given chemical pump 34A feed rate (fa),
chemical pump 34B feed rate (fb), FCI (L), and blowdown flow rate
(B), the successive "states" (PO', Na concentrations) that the
boiler can assume over time trace out a curve in this state space.
Generally, the boiler state space permits the visualization of
both the phosphate exponential (Equation #2) and the sodium
exponential (Equation #3) simultaneously depending on their
respective current concentrations and the steady state
concentrations.
In particular, the curves begin at the point representing the
current P0~ and Na boiler c.~n~entrations. Furthermore, according
to Equations #2 and #3, the ~oiier ~~oncentr«tions must attain their
steady state levels (t=~), namel.y:
28

CA 02415685 2003-02-04
P04(t=~) - (fa*P04a + fb*F04b)/B (Equation #4)
Na(t~) - (fe*Nae + fig*Nab + L) /B (Equation #5)
Multiplication of both sides of the above equations by B shows that
they simply state that, in equilibrium, the flow of P04 or Na into
the boiler equals the flow out of ..t.
Hence, the curve is defined t5y a straight line between the
initial point (PO4(n), tda(0)) a,nd the final point (P04(~), Na(~)).
The reason for this is that in the time domain Equations #2 and #3
the same time constant, r, (M/B), governs the exponential approach
to the equilibrium concentration For both phosphate and sodium.
Thus the fraction of the total difference between the starting and
final concentrations after any timEa t i ~ the same for both PO~ and
Na; changes in "y" (Na) sate always in the same proportion to
changes in "x" (PO4) -- the definition of a straight line. Just as
its x and y components are, likewise the exponential approach of
the point itself (which is the superposition of these "x" and "y"
components) to the equ.ili~rium point in state space is also
governed by this same time _constant.
Note that, although the line traced out is straight, the rate
at which the point moves along the., line varies. In the beginning
the point moves at the fastest rate; as it asymptotically
approaches the final equilil,rium point the velocity approaches zero
(which corresponds with t:.~e exponential functions in the time
domain, :i.e., the derivar_ivn oY each exponential. yields its
greatest velocity at the initial state and is zero at the
equilibrium point). In fact, the time at which any fraction of the
total distance along that line will be traversed is given by using
29

CA 02415685 2003-02-04
Equations #2 and #4. The following equations provide the time that
corresponds to a particular fraction of the total distance to the
equilibrium point that remains to be traversed:
t = -(M/B)*ln((P04(t) - P04~~))/(P04(0)-P04(«~))) Equation #6
and using Equations #3 and #5 for sodium,
t = -(M/B)*ln((Na(t) - Na(~>)/(Na(0) - Na(~))) Equation #~
These simple, straight line, state space trajectories provide a
method of considering the entire range of boiler concentrations
that are attainable from a give=_n starting point using a particular
set of feed rates.
The possible "end points" of these straight lines (Eauations
#4 and #5) are defined by t:he range of the chemical feed pumps 34A
and 34B:
famin <- fa ~= famax w'he.re famin is usually 0;
fin <= fb <= fix where fin is usually 0.
Recasting Equations #4 and =5 into a vector format, and in light of
the above constraints, the set of all possible end points (steady
state feed rates) forms a parallelogram (i.e., a linear combination
of the two feed concentration vectors, fa and fb) in the state space
hereinafter known as the pumpable region 62:
P04(~) (P04~,/B) (P04e/Bj (0j
f8 + fb + (Equation #8)
Na(~) (Naa/B) (Nat~/H) (L/B)
This pumpable region 62 is shown in Figure 6. Note that each point
in the pumpable region 62 ~zniquely defines the feed rates (fa, f~)
associated with the corresponding steady state boiler
concentrations, provided only that the concentrctions in chemical

CA 02415685 2003-02-04
pumps 34A and 34B are linearly :independent (in other words, P04e*Nab
- P04b*Nae must not be equal to 0), This is an important
requirement of the MACC controller 52.
By !:olding the feed rates constant, the boiler state space
point is moved along a straight line towards the final, steady-
state, concentrations associated with those feed rates. Moreover,
the time it takes t.o reach any intermediate point along this state
space trajectory is given via Equations #6 and #7. Determining the
pumpable region 62 by the MACC controller 52 is known as "scaling
the map" and once this is completed, the controller 52 is ready to
proceed with determining stage 1 of the feed program.
The objective of stage 1 of the feed program is to bring the
boiler water concentrations into the target region 60 as quickly as
possible, as discussed earlier with respect to Fig. 5. Thus, it is
necessary for the MACC co:~troiler 52 to determine a state space
trajectory 64 that begins at the current boiler water
concentrations 54, and crosses into the target region 60, as shown
in Fig. 6.
However, the only state space trajectories 64 available are
those that correspond to actual feed pump rates, in other words,
line segments which begin at t:he current point 54 and end in the
pumpable region ~52. In general, some of these segments (Fig. 7)
will cross into the target region 60, and some will not. In the
special case in whic!: none of these line segments cross into the
target region 60, the flag E-BOXUNREACHABLE is set, and the MACC
controller 52 uses those feed rates that will drive the current
31

CA 02415685 2003-02-04
boiler fluid concentrations 54 towards the area of the oumpable
region 62 that is closest to the target region 60.
Otherwise, at least one such segment/trajectory 66 crosses
into the target region 60. By virtue of Equations #6 and #7, if the
end point 68 of this trajectory 66 is not on the perimeter of the
pumpable region 62, the time that: it takes to reach the target
region 60 can always be reduced by replacing this end point 68 by
the point '~0 on the pumpab°..e region's 62 perimeter found by
extending the line segment 66 beyond the original end point 68.
Intuitively, by making the distance 66 to the target region 60 a
smaller fraction of the entire trajectory 72, the time to reach the
target region 60, which depends only on the fraction of the total
trajectory that needs to be traversed, is reduced. Kote that any
such trajectory 66 will reach the perimeter of the target region 60
before it reaches the interior; thus "'target region" and "target
perimeter" are equivalent.
Alternately, when this "endpoint projection in the state
space" is viewed in the time domain, by the MACC controller 52
selecting a feed rate ccmbination that will yield equilibrium
concentrations beyond the desired equilibrium concentrations, the
exponential will reach the desired equilibrium concentrations in a
shorter time than if the exact: feed rate combination for the
desired equilibrium concentration were uved (Fig. 81.
The next determination that the M.ACC controller 52 makes is to
determine just which pumpable region perimeter point will enable
the boiler state space point to reach the target region 60
32

CA 02415685 2003-02-04
perimeter in the fastest time. To accomplish this, the MACC
controller 52 considers those trajectories that both cross a given
edge 74 of the target region's 60 perimeter and end on points in
the pumpable region 62 that 1 ie on a lire parallel to that edge 74.
All such trajectories reach the given edge 74 in exactly the same
time. To see this, recall the following theorem of Euclid: if two
straight lines in a plane are cut by three parallel lines, the
corresponding segments are proportional. As shown in Fig. 9,
because of this theorem and Equations #6 and #7, the time required
to reach the target edge 74 is the same for. any two, and thus all
such trajectories.
This time can be minimized by choosing end points within the
pumpable region that lie on lines parallel to the target edge 74
that are as far away as possible from the initial point (again,
c.f. Equations #6 and #7). This implies that the end point of the
shortest time trajectory either lies on a vertex of the pumpable
region 62, or on one of the trtijectories that passes through the
endpoints of the target edge (Fig. 10). As can be seen in Fig. 10,
there are eight line ~egment~5, each having twu collinear
trajectories that must be evaluated. The MACC controller 52
exploits this fact by checking each of th.e, no more than 16, such
trajectory scena~_-ios, and then u~,i~ig Equation #6 to select the one
that reaches the target perimeter in the least time. The 16
candidate trajectory scenarios are established by this analysis
being performed on all four target region 60 edges. The 16
candidai.e trajectories caa be more easily seen in Figs. 10A and 10B
33

CA 02415685 2003-02-04
where each indicated intersection point defines a particular
candidate trajectory; the location of the target region 60 with
respect to the pumpable region 62 has been altered for- clarity.
The optimization problem and solution exploits the so-called
fundamental theorem of linear programming; the function to be
optimized in the linear programming problem--which, though non-
linear, has the monotonicity (i.e., either continuously increasing
or continuously decreasing) along straight lines in state space
that the fundamental theorem of linear programming requires--is
Equation #6 (or #7).
Compared to the complexities of stage :l adjustment, stage 2 is
easier. As shown in Fig. 11, first, the current point 54 is
replaced with the point 68 on the ;.arget region 60 perimeter found
via stage 2, which represents where the boiler concentrations will
be at the end of stage 1. This new current point and the setpoint
58 determine a line segment 84; the point on the pumpable region's
perimeter found by extending this line segment beyond the setpoint
58 must, by the argument given earliery correspond to the feed
rates ( faz, f~) that reach the setpoint 58 in the least 'time. If
there is no such point, the "~LACC controller 52 sets the
E POINTUNRRACHABLE flag and uses the feed rates that correspond to
the pumpable point cnithin the ta~.~get region 60 that is closest to
the setpoint.
For stage 3, the MAC~:~ controller S? simply feeds at the feed
rates that c:~rrespond t.o the setpoint (f3,,, f~). Stage 3 remains in
34

CA 02415685 2003-02-04
effect until the next sample comes in. The durations of stages 1
and 2 are determined via Equation #6 (or #%)~
For some applications, it may be desirable to modify the MACC
controller 52 algorithms, a~~ discussed below.
Blowdown and FCI Calculation: The blowdawn (B) and FCI (L)
estimates are exact: they are the roots of equations that have at
most one solution. This could introduce prcblems in boiler systems
where the time between pH and P04 samples is much less than the
boiler's residence time. Attempting to feed such data into the
current MACC controller 52 will lead to a series of E_INDETERMINATE
flags which, in effect, ttErow away the intermediate data points
until enough time has elapsed so that a meaningful feed program
determination can be made. Therefore, zn boiler systems that
utilize such smaller sampling times, it would be more efficient to
fit the blowdown and FCI estimates to a series of phosphate and pH
measurements; those blowdown and fCI estimates that came closest to
the phosphate and pH sequences in a least squares sense could then
be used. This change woul~:.3 dive the MACC system 20 the desirable
property that more freguer;t operator sampling would always result
in better control.
Introduction of Uead Time: Another way that the MACC system 20
could be improved is by Pxplicitly including dead time into the
model. The MACC controller 52 currently assumes that the dead time
is zero. Limited simulations suggest that this simplification will
not degraue control mucri, provided that dead time i~ a small
percentag::. ~f the boiler residence time. However, the impact of

CA 02415685 2003-02-04
dead time depends on many factors; it is lil~ely that at least some
of the controlled boilers would experience significantly better
MACC control than they would have otherwise obtained had dead time
been not been included in the model. On the other hand,
conventional congruent control does not explicitly incorporate dead
time either.
Optimal Feed I?rogram Determination: the linear programming-
related-opti~aization of the MACC controller 52 feed program
determination method could be generalized in a number of ways.
Firstly, the setgoint of stage 2 could be replaced with a smaller
target region, and then the same computation as in stage 1 could be
performed to reach it. This would amount to putting a dead band
around the setpoint. In particular, this would entail setting up a
new target region around the setpoint and analyzing new feed rate
trajectories between the new target region vertices/pumpable region
vertices and the new boiler fluid chemical concentrations in a
manner similar to that shown in Figs. 10A and 10B. Once the new
target region is achieved, the feed rates corresponding to the
setpoint would be maintained. Secondly, there is also no reason to
have just two such adjustment stages--there could be any number of
target regions, each with its corresponding adjustment stage.
Thirdly, there is no need to 1 i mit the appl ication of this feed
program optimization to ;;ust two dimensional state spaces. For
example, assuming that. polymer measurements were possible, one
could introduce a three dimensional state space/target region; if
36

CA 02415685 2003-02-04
such a system were still limited to two pumps, this would result in
a planar pumpable region being embedded in a 3-B space.
Ammonia Correction: Boilers occasionally have ammonia returned
to the boiler. Once in ,'.he boiler, ammonia masks the normal
relationship between the pH and sodium that is used to control the
congruency. Ammonia affects the pH. With ammonia present in the
boiler water and with no ammonia correction as part of the MACC
system 20, the sodium concentration calculated from the measured pH
will be higher than is actually correct.
Thus, the following providNS a method for correcting the
measured pH for the effect. of ammonia: an analytical determination
of the ammonia in the boilE~r wader is provided by any conventional
ammonia measuring means (e.g., colorimetric, ion-selective
electrode, ion chromatography) to the MPCC controller 52. This
ammonia concentration is incorporated into the electroneutrality
equation mentioned above, thereby accounting for the ammonia charge
contribution and, in particular, subtracting the effect of the
ammonia ions from the calculated sodium. tlse of the ammonia
correcting method can be implemented 3s an option in the MACC
system 20 that the operator ~~an select so that when the pH and
phosphate analysis data ~s inputted into the MACC system 20, the
ammonia analysis data can also be entered.
On-Line Input Means: Many boiler operators desire having on-
line instrumentation (pH fieters/phosphate analyzers) for boiler
water parameters rather than using manual data entry of timse
parameters. Although one of the novel aspects of the MACC system
37

CA 02415685 2003-02-04
20 is that it eliminates the need to have an-line instrumentation
which is susceptible to breakdown and requires frequent
calibration, the MACC system 20 i~> easily adaptable for such on-
line instrumentation in the input means 40.
Three types of on-line measurements are suggested: (1) on-line
blowdown flow measurements coupled with manual pH and phosphate
concentration measurements (hereinafter referred to as oLBF) and
(2) on-line blowdown flow coupled with on-line pH and/or on-line
phosphate (hereinafter referred to as OLBF/pH or OLBF,/P04) and, {3)
on-line pH measurement coupled with manual phosphate (hereinafter
referred to as OLpH). In the third type, blowdown is estimated
rather than measured on-line.
In OLBF, the phosphate concentration and pH of the boiler
water would still be manually entered every 8 to 24 hours. Between
the P04 and pH entries, alI blowdown flow measurements and the
continuously varying feed rates resulting from the MACC system 20
calculations would be stored in arrays. These stored blowdown flow
values would comprise appropriately filtered values chosen to
average out short-term variation.
As discussed earlier, after each entry of pH and phosphate i5
made, the MACC system 2!) estimates the blowdown flow and FCI.
Using the blowdown flow measurements made over the interval between
samples, the MACC controller _'>2 can use the successive manual
phosphate entries to estimate a boiler phosphate mass imbalance in
a manner analogous to the way in which the FCI is estimated. The
boiler phosphate ~ass imbalance represents a measurement of any
38

CA 02415685 2003-02-04
discrepancy between the expected boiler water phosphate mass and
the measured boiler water phosphate mass. The principle difference
would be that, for both the FCI anti phosphate imbalance estimates,
Equations #2A, #2B, and #2C w~~uld be expanded to included
additional stages so as to incorporate changes in the measured
blowdawn flow into the model predicaed boiler sodium and phosphate.
The feed program updates for the OLBf method occur much more
frequently than for the manual method. with the manual method,
this estimate will occur after each entry, every 8 to 24 hours.
With the OLBF method, these feed program updates are made after
every blowdown flow measurement using the current blowdown flow and
the last set of manually entEred pH and PUS values. Because there
are N blowdown flow samples, where N is the number of distinct
blowdown samples (and different feed rates) between pH and P04
samples, there will be N feed program updates between pH and P04
samples.
As with the MACC system 20, the optimized feed program would
be computed using a state space diagram,, except that the diagram
would be scaled (c. f. Equation #8) using the currently measured
blowdown rate. The initial phosprate and sodium concentrations to
be used would be the boiler model-projected phosphate and sodium
concentrations; the expanded form of Equations #2A, #2B and #2C
(see above), incorporating the ~~LBF measurements, would be used to
determine these project.ians. fhe resulting calculated feedrates
would then be transmitted to tree pumps by the control means 32.
39

CA 02415685 2003-02-04
As also discussed earlier, the MACC system 20 uses three
stages to reach the set.point 58. Due to the long time between
samples, it is possible that all three stages may be reached in one
sampling interval. However, using the OLBF method, there could be
as many as 3*N stages each time 'the state space and feed program
are updated with a new OLBF measurement, three new stages would be
generated. In reality, be~:.ause the updates would be so frequent,
the system '0 would rarely gzt beyond the current stage before
another update arrived.
With OLBF/pH or OLBF/P04, the same steps discussed with OLBF
would be carried cut. Then, using the measured pH, the boiler water
sodium concentration would be estimated. If an on-line P04
analyzer is used with the MACC system 20, the boiler water sodium
concentration can be calculated as before using the charge balance
equation. Otherwise, the last phosphate measurement, the on-line
blowdown flow measurements and feed rates (obtained since that last
phosphate measurement) are then used, along with the expanded form
.of Equations #2A, #2B, and #2C, t.o obtain a model-projected
phosphate. Using tht ~~harge balance equation as before, this
model-projected phosp:iate arid the on-line temperature corrected pH
measurement are used to estimate a boiler water sodium
concentration. In either case, the resulting sequence of boiler
phosphate concentratio:~s and/or e:~timated boiler sodium
concentrations are used, along with the differential mass balance
equations shown below; to estimate PO~ and/or FCIs.
M*(d(Na(t))/dt = L_Na!t) + fa(t)*Naa + fb(t)*Nab -B(t)*Na(t)
Fquatzon #9

CA 02415685 2003-02-04
M*(d(P04(t))/dt = LiP04(t)+fR(t)*P04s + fb(t)*P04b -B(t)*P04(t)
Equatzon #10
The above equations are solved for L Na(t) and L_P04(t), and these
instantaneous L_Na(t) and L.~PO4(t) estimates are averaged/filtered
in such a manner so as to balance the competing goals of responding
quickly to FCIs vs. estimating those FCIs accurately. If the FCIs
are tracked closely, the upstream series (fa(t), fb(t)) needs to be
shifted to incorporate deadtime explicitly; otherwise, it is
necessary to use averaged,/fil.tered L_Na(t) and L-P04(t) that are
representative of time interval ~ large enough so that the impact of
deadtime on these averaged /filtered estimates is negligible.
These averaged/filtered L~Na(t) and L~PO4(t), and blowdown
flow(t) are used, along with either the measured or model projected
current boiler phosphate and sodium concentrations, to continuously
update the MACC system ?0 state space diagram and associated
optimized feed program.
With the last option, OLpH, a similar set of calculations are
conducted. The MACC system 20 model boiler parameters are updated
as before whenever manual phosphate analyses are entered. The on-
line pH and model-projected phosphate concentration are used to
continuously update the F'~.:I estimate and thereby the MACC system 20
feed program, in a manner analogous to OLBF/pH, but using
estimated, rather than measured, blowdown flow rates.
It should be noted that with all of these on-line MACC system
20 options, control of the chemistry is subject to the normal
deadtime constraints pre>ent with any automated control scheme.
41

CA 02415685 2003-02-04
Multiple Boiler Control: The MACC system 20 is readily
adaptable to controlling multiple (e.g., ten) boilers 22 at a
customer's facility. ~'o that end, the particular boiler parameters
(e.g., boiler size) can be manually entered into the MACC system 20
and the two feedstreams 30A and 30B can be controlled through a
solenoid (not shown) that switches the feedstream flows to a
particular boiler based on boiler water chemistry, i. e. , the boiler
water that is in the lease desirable state will receive the MACC
system 20 congruent control initially.
Note: the definition of the word "amtomatic" as used in this
specification with respect to the MACC system 20 refers to all
operations, actions, calculations, pumping and other determinations
but excluding the manual :removal of the blowdown sample from the
blowdown flow, manual insertion of the :sample into the analyzer,
and operator entry of the chemical data into the computer 42. In
other words, the presence of human intervention in one aspect of
the invention does not n:rgate the automatic operation based on a
manually-entered in~~ut (e. g., measured phosphate concentration).
In addition, operator response to automatically-generated error
messages, alarms, etc. dcmut negate the automatic characteristics
of the invention.
As shown in Fig. 12, there is shown a second embodiment of an
automatic congruent controller system 220, hereinafter known as the
ON/OFF control system 2.'0. As with: the MACC system 20, the ON/OFF
control system 220 cont:.~~ol~: two interdependent variables, namely,
phosphate and sodium, t:-ae :utter by way of monitoring the pH.
42

CA 02415685 2003-02-04
As stated earlier with respect to the MACC system 20, all
subsequent references to sodium and/or to the sodium-to-phosphate
ratio (Na/P04) of the boiler water refers to that sodium which
interacts with the phosphate to maintain the boiler water so as to
inhibit corrosion. This ~s also referred to as the "effective
sodium" or the "effective sodium-t:o-phosphate ratio."
The ON/OFF control system 2:0 is arranged to control water
treatment chemicals'to be intx-oduced into .3n industrial boiler 222
by way of the feedwater 224 to the boiler 222. The boiler 222 has
an effluent flow (hereinafter kn~~wn as blowdown flow 226) and a
blowdown valve 228. As stated previously regarding the MACC system
20, the ON/OFF control system 220 does not control the blowdown
flow 226 via the blowdown valve 228. In fact, one of the
distinguishing features cf the ON/OFF control system 220 over
conventional boiler fluid control systems is that the blowdown flow
226 varies independently of the c~N/OFF control system 220.
The system 220 basically comprises a first chemical feedstream
230A, a second r_hemical feedstream 2308, a control means 232 and an
input means 240. The first chemical feedstream 230A and the second
chemical feedstream 2308 are each connected to the feedwater line
224. The first chemical feedstream 230A is arranged to deliver a
first fluid treatment Material or chemical, e.g., a sodium
phosphate mixture having a:~ parr ir_ular sodium-to-phosphate ratio and
a known pr:osphate concentration, to the water in the boiler 222.
Similarly, the seccnd cht:;icai. feedstream 2308 is arranged to
deliver a second fluid t~eatme~nt material or chemical, e.g., a
43

CA 02415685 2003-02-04
sodium phosphate mixture having a particular sodium-to-phosphate
ratio and a known pho;~phate ccncentration to the water in the
boiler 222. It is important to rote at this juncture that the
sodium-to-phosphate ratio in the first chemical feedstream 230A
must be different from the sodium-to-phosphate ratio in the second
chemical feedstream 2308 while the respective known phosphate
concentrations in the feedstreams can, in certain circumstances, be
identical. The particular sadium-to-phosphate ratio (Na/PO')
determines a particular pH fcr that fluid treatment material.
Where the known phosphate concentration ~F04) in both feedstreams
is identical, then the different sodium concentrations (Na) in each
feedstream determine the pH (hi.gh or low) of each fluid treatment
material. The importance of the pH is that the pH of the boiler
water, at a fixed phosphate concentration, is indicative of the
sodium concentration in thr~ boiler water. Thus, monitoring the pH
of the boiler water provides an effective method of monitoring the
sodium-to-phosphate vatic of the boiler water and then in
determining which of the two feedstream sodium phosphate mixtures
is to be fed to the boiler water to adjust the sodium-to-phosphate
ratio of the boiler water, as will be described below. As shown in
Fig. 13, by fixing the amount of phosphate in the boiler 222,
varying the amount of sodium perrlits the sodium-to-phosphate ratio
of the boiler water to be controlled.
Each feedstream 230A .and 230B includes an electrically driven
pump 234A and 2348 which are coupled t:o the outlet of respective
chemical feed tanks 23E:A and 2368, via respective draw down
44

CA 02415685 2003-02-04
assemblies 238A and 238B. The chemical feed tanks 236A and 236B
contain high-pH or low-pH sodium phosphate fluid treatment
materials, respectively. The draw down assemblies 238A and 238B
act as accumulators for enabling precise control of the pumps 234A
and 234B by the control means 232..
The control means 232 is arranged to precisely control the two
chemical feedstreams 230A and 230B by contrclling the operation of
the pumps 234A and 234B in order to achieve and maintain a
predetermined desired sodium-to-pho:~phate ratio in the boiler water
while maintaining a predetermined fixed phosphate concentration
(POD setpoint) in the boiler water. In particular, in response to
the measurement of the boiler water pH provided by way of input
means 240, the control means 232 adjusts the boiler water sodium-
to-phosphate ratio by selecting one of the two feedstreams 230A or
230B to pump the appropriate sodium phosphate fluid treatment
material while ensuring that the selected feedstr_eam :?30A or 230B
pumps at a rate proport:.oval to they blowdown flow 226 in order to
maintain the fixed phosphate concentration.
The control means 232 basica~.ly comprises a computer-based
control unit 242 such as that sold by Betz Laboratories, Inc. under
the mark SMARTSCAN Plus and <associated :~oftware/firmware 244 and a
monitor/keyboard 246. The CN/nFF c-entrol system software 248 and
SirIARTSCAPI Plus software 244 far effecting the operation of the
control means 232 is set forth :Ln Appendi.x B. The control means 232
also includes a feed pump controller 250 and the associated draw
down assemblies 238A and 2388.

CA 02415685 2003-02-04
The feed pump controller 250 and the associated draw down
assemblies 238A and 238B are constructed in accordance with the
teachings of U.S. Patent No. 4,897,797, assigned to the same
assignee as this invention, namely Betz Laboratories, Inc., and
whose disclosure is incorporated by reference herein.
As will be discussed in detail below, the ON/OFF control
system software 248 evaluates the boiler water pH and compares that
value to the pH setpoint that is entered into the computer 242 via
the keyboard 246 by the operator. If the pH is above or below the
pH setpoint, the ON/OFF control system software 248, via the
computer 242 software/firmware, will command the feed pump
controller 250 to drive the appropriate pump 234A or 2348 to
correctly feed the precise amount of chemicals that will achieve
and maintain the desired phosphate concentration and the desired
sodium-to-phosphate ratio.
The input means 240 basically comprises a blowdown flowmeter
252, a pH meter 254 and an optional phosphate analyzer 256. The
blowdown flowmeter 252 measures thf~ blowdown rate of the boiler 222
and provides a corresponding electrical signal on line 258 directly
to the feed pump controller 250. As will be described later, the
feed pump controller 250 uses this signal to ensure that whichever
feedstream 230A or 230B s delivering its respective chemical
mixture to the boiler 2:?2, thEa chemical mixture xeea is m
proportion to the blowdown flow 226. In particular, the ON/OFF
control system software X48 implements this proportion by the
following equations:
46

CA 02415685 2003-02-04
Lo_Na_flow = P04_setpoir~t * (BD flow)/(Lo PO4 conc)(Equation #11)
Hi_Na_flow = P04_setpoir,t * (BL~4flow)/(Hi_PO4_conc)(Equation #12)
where,
Hi_Na_flow: pump 234A pumping rate;
Lo_Na_flow: pump 234B pumping rate;
P04_setpoint: predetermined fixed phosphate concentration
BD flow: current blowdown flow measurement or exponential
moving average of past n flow values;
Hi_P04_conc: known phosphate concentration in tank 236A;
Lo'P04'conc: known phosphate aoncE:ntration in tank 2:368;
The pH meter 254 monitors the pH of the blowdown flow 226 and
provides a corresponding eiectrical signal on line 262 to the
computer 242 for processing by the ON;!OFF control system software
248. As is also well known, the pH of the blowdown flow 226 is
indicative of the boiler water pH.
A cooling coil 264 is disposed between the blowdown flow 226
and the pH meter 254, as well as the optional phosphate analyzer
256 (if used), to protect these apparatus from damage due to the
high temperature of the blowdown flow 226.
Control in this ON/OF'F control system 220 resides in two
places. The first area of control is by way of a maintenance means
that maintains a fixed concentration of a chemical, e.g.,
phosphate, in the boiler water. In particular, the maintenance
means for maintaining a fixed phosphate concentration in the boiler
water comprises the feed pump controller :?50, the two feedstreams
230A and 2308 and the bl.owdown flcwmeter 252. Since the phosphate
concentration i.-~ each chemical fef.d tank 236A and 236B is known and
the predetermined fixed phosphate concentration (P04 setpoint) has
been entered into the comf.~uter 242 by the operator, the feed pump
controller 250 uses the :~igr.al 258 to control the pumps 234A and
47

CA 02415685 2003-02-04
2348 so that a precise amount of phosphate is delivered to the
boiler water to feed the amount of phosphate needed to maintain the
phosphate setpoint in steady state, in accordance with the
previously discussed algorithm. In accordance with one preferred
aspect of this invention, the ON/OFF control system 220 operates to
ensure equal usage of tanks 236A and 2368, i.e., an even
distribution of tank amount is used to avoid emptying one tank
faster than the other.
The second area of control i;s through the switching between
high-pH and low-pH sodium phosphate mixtures of the first chemical
feedstream 230A and the second chemical feedstream 2308. This
action is conducted by the control means 232. In particular, the
ON/OFF control system software 24E~ may use, but is not required to
use, an exponential moving average (EMA) of the pH (@ 25°C) to
minimize electronic noise from the pH meter 254 in providing the
boiler water pH value. A pH adjustment is made at predetermined
intervals, e.g., every 30 minutes. If the EMA pH value is above
the pH setpoint, as determined by a comparator means in the ON/OFF
control system software 248, the software 248 commands the feed
pump controller 250, via the computer 242, to control the pump 2348
to feed the law-pH sodium phosphate mixture i.n the second
feedstream 2308 at the ~aroportioned rate (Equation #11) while
turning off the other pump 23~A in the first chemical feedstream
230A, thereby driving thbollsar water pH value down to the pH
setpoint. If the EMA pH value is below or equal to the pH setpoint
as determined by the same comparator means in the ON/OFF control
48

CA 02415685 2003-02-04
system software 248, software 248 commands the feed pump controller
250, via the computer 242, to control the pump 234A to feed the
high-pH sodium phosphate mixture in the first feedstream 230A at
the proportioned rate (Equation #1.2) while turning off the other
pump 2348 in the second chemical feedstream 2308, thereby driving
the boiler water pH value up t« the pH setpoint. In either
situation, whichever feedstream is delivering its particular sodium
phosphate mixture, the feed pump co,ntrollcr 250 maintains the fixed
phosphate concentration in the boiler watAr by ratioing the active
feedstream 230A or 230B to the bl.owdown rate. This alternate "on"
and "off" feed control brings the boiler water sodium-to-phosphate
ratio to the desired sodium--to-phosphate ratio at the predetermined
fixed phosphate concentration and maintains that ratio at that
phosphate concentration. :In parti~~ular, tt,,e ON/OFF control system
software 248 implements thi:~ switching by the following commands:
IF(pH > pH-setpoint) THEN
Lo_Na_flow = P04_setpoint * (BD_flow)/(LO-P04 conc)
Hi_Na_flow = 0
ELSE
Lo_Na_flow = 0
Hi_Na_flow = P04-setpoint * (BD_flow)/(Hi_P04 cone)
where,
pH: current pH value of boiler water or exponential weighted
moving average of n past pH values;
pH_setpoint: pH setpo:i.nt
Should the first chemical feedstream 230A and the second
feedstream 2308 ever be utilized ;simultaneously so that both are
delivering their respective sodium phosphate mixtures to the boiler
water, rather than in alternation as in the ON/OFF control system
220, then the sum of the feedstream _rates (Hi Na_flow + Lo Na_flow)
49

CA 02415685 2003-02-04
would have to be in proportion to the blowdown flow 226 in order to
maintain a fixed amount of phosphate in 'the boiler water.
The measurement of the blowdown pH can be a single measurement
or can be the average pH value of a plurality of pH measurements.
In the ON/OFF control mode, it. is assumed that the blowdown
flow rate accurately maps the changes in boiler phosphate
concentrations. It is posJible that the expected and actual
phosphate concentrations can start to diverse due to calibration
problems, pump leaks, or actual leaks in the boiler. In that case,
some means of monitoring the concentration and informinS the
operator that action to corre<~t the boiler phosphate mass imba Lance
is desirable. One exemplary method «f detecting the mass imbalance
entails use of a phosphate analyzer 256. To that end, the analyzer
256 monitors the phosphate concentration in the boiler water and
provides an electrical signal on line 260 to the feed pump
controller 250 which, in turn, provides this electrical signal to
the computer 242. The computer 242 provides the phosphate
concentration to the ON/OFF control system software 248. The
0N/OFF control system software 248 calculates the statistical
variation in the desired phosphatE=. concentration over time and
alerts the operator to a boiler water phosphate mass imbalance. 1s
an alternative to using the phospuate analyzer 256, the blowdown
phosphate concentration ~~oula be manually measured and the measured
phosphate concentration can be entered into the computer 242 at
various time intervals.

CA 02415685 2003-02-04
Without further elaboration, the foregoing will so fully
illustrate our invention that others may, by applying current or
future knowledge, readily adopt the same for use under various
conditions of service.
~1


Image

CA 02415685 2003-02-04
/* tnacc4.h: external interface for the mace4.c module */
/* M~ ?9, 1994: added nh3, lastnh3, and b4lastnh3 to support ammonia
co__ection to the pH -- JCG */
# include <float.h~ /* uses only FLT,MAX */
# define E ENGINEERI1~1G UNITS :1. /* 0=research units, 1=engineering units*/
# define E NAN (-l.Oe3~? /* Not a number */
# define E_MINMINP04 (1.0e-3/94.97? /* 1 ppm in moles/kg */
# define E MAXMAXP04 (1.0e-1/94.97) /* 100 ppm in moles/kg */
# define E MINMINNAP04RATI0 2.2
# define E_MAXMAXNAP04RATI0 2.8
# define E MAXHISECT:1:ONS 100 /* aprox. 1 part in 1.26765e+30 */
# define E NV 4 /~~ number of vertexes in feasable region, V */
# define E NW 4 ;* number of vertexes in target region, W */
typedef enum e_logica~l { E FALSE=0, EJTRUE=1 } E LOGICAL;
typedef struct a vec:t=or { dou:~le po4; double na; } E VECTOR;
typedef enum e-initst:atus { !* e_init() errors/alarms */
E_INITOK=0,
E _MISSING T,
E_MISSING FAMIN,
E MISSINC3 FAMAX,
E__MISSING FADEF,
E _MISSINC3 P04A,
E MISSING RATIOA,
E_MISSING FBMIN,
E MISSING FBMAX,
E MISSING FBDEF,
E MISSINC3 P04B,
E_~MISSIN(i RATIOH,
E_MISSING P04,
E MISSING PH,
E ~MISSINC3 M,
E MISSIN(J BDTEMP,
E MISSING P04SETPO::NT,
E~MISSING MINP04,
E_MISSING MAXP04,
E MISSING RATIOSET~?OINT,
E BDMAX LT BDMIN,
E~_NALEAKMAX LT NALF'sAKMIN,
E FAMAX LE FAMIN,
E_FBMAX LErFBMIN,
E MINP04_L~ MINMIN~?04 ,
E~MAXP04 GT MAXMAXP04,
E_P04SETPOINT LT MINP04,
E_~P04 SETPOINT GT MAXP04 ,
E P04A LT ZERO,
E__P04 B LT ZERO ,
E MINRATIO LT MINM:CNRATIO,
E~MAXRATIO GT MAXMAXRATIO,
E~_RATIO_LTrMINRATIO,
E RATIO GT MAXRATIO,
E~__RAT I OA EQ RAT I OB ,.
E _MISSING S$GA,
E MISSING SPGB,
E II~fITSTATUS;

CA 02415685 2003-02-04
t~~~edef enum a updatestatus { /* a updates) errors/alarms */
E UPDATEOK=0,
E_~ ~'7ETERMINATE=:L,
h L..X UNREACHABLE=2,
E_OUTOFBOX TOOLONG=4,
E_POINT UNREACHABLE=8,
I._BOX UNMAINTAINABLE=16,
E POINT UNMAINTAI1VABLE=32,
E SAMPLRINTERVAL 'r00LONG=64 ,
E~_NOTUPDATED=128,
E INCONSISTENT PO~~=256,
~ INCONSISTENT PH:=512
E-UPDATESTATUS;
typedef struct a /*
boiler { public:
*/


double t; /* time
since
start
of
run
*/


double famin, /* pump
a
=>
mininum
feed
rate
*/


famax, !* maximum
feed
rate
*/


fadef, /* .
default
(initial)
feed
rate
*/


po4a, /* total
P04
(as
ortho-P04)
concentration*/


ratioa, /* Na/P04
ratio
*/


spga; /* specific
gravity
*/


double fbmin, /* pump
b
=>
mininum
feed
rate
*/


fbmax, l* maximum
feed
rate
*/


fbdef, !* default
(initial)
feed
rate
*/


po4b, ,%* total
ti /* P04
b (as
~rtho-P04)
concentration*/


ra Na/P04
o ratio
,


spgb; /* specific
gravity
*/


double m; /* boiler
water
mass
*/


double bdtemp; /* boiler
blowdown
temperature
*/


double po4, /* boiler
blowdown
po4
=>
concentration
*/


dpo4, /* variability
*/


po4setpoint,/* setpoint
*/


minpo4, /* ~
lower
control
limit
*/


maxpo4; /* upper
control
limit
*/


double nh3; /* boi.l~er
blowdown
ammonia
concentration
*/


double ph, /* boiler
blowdown
pH
=>
at
temperature=bdtemp
*/


dph, /* variability
*/


phsetpoint, /* setpoint
(nh3=O.O;bdtemp=25C)*/


dphsetpoint;/* 1/2
setpoint
control
range
*/


/* (control
limits
=
phsetpoint
+/-
dphsetpoint)*/


double napo4ratio, /*
alternative
to
phsetpoint
*/


minnapo4ratio, /*
alternative
to
phsetpoint-dphsetpoint
*/


maxnapo4ratio; /*
alternative
to
phsetpoint+dphsetpoint
*/


double max_sample"interval;
/* if time
between
samples
is longer
than*/


i*
this,
an
updatestatus
code
is
flagged
*/


double max_outofboxadj ustment;
/*
if
we
cannot
get
into
control
ranges
on
*/


~ *
pH
and
P04
within
this
time,
an
*/


/*
updatestatus
code
is
flagged
*/


double max relativeerr or;
/*
largest
allowed
variation
in
estimated
*/


/* steady-state ntrations
conce induced
by
the
given
variabilities,
dpH
and
*/


/* dP04 ERMINATE
before status
an E_INDET ~s
flagged
*j


double beps, j*
bisection
(root
fording)
epsilon
*/


bdmin, /*
lower
bound
for
blowdown
bisection
*/


bdmax, /*
upper
bound
for
blowdown
bisection*/


naleakmin, j*
lower
bound
for
Na
leak
bisection
*/


naleakmax; i*
upper
bound
far
Na
leak
bisection
*/


/* (final rootfinding due to truncation errors will be */
errors
on


/* bep s*(~bdmax~ ~bdmin~) beps*(~naleakmin~+~naleakmax~) */
+ and


double fdt; /* f eed delta
time
(due
t:o
hardware
truncation):
*/


/* m inimum
tame
interval
between
pump
changes
*/




CA 02415685 2003-02-04
/* private: */
double lastt, lastbdtemp, lastpo4, lastnh3, lastph; /*last inputs... */
dog ' b4lastt,b4lastbdtemp,b4lastpo4,b41astnh3, b4lastph;/* & before last */
dou.,~e bd, 1; /* blowdown, leak estimates */
double lastbd, lastl; /* prev blowdown, leak estimates */
double dt[2], fb[~:1, fa[3]; !* current feed program */
double lastdt [2] , :Lastfb [3] , lastfa [3] ; /* previous feed program */
E__VECTOR vV[E NV+ll, vW[E NW+1]; /* pumpable, target regions */
E VECTOR vlastV[E IW+1], vlastW[E NW+1]; /* last pumpable, target regions */
E-_-_VECTOR vF[3]; -- /* steady-state concentrations of the feed program */
E VECTOR vlastF[3:; /* last steady-state concentrations of the feed program*/
E UPDATESTATUS updatestatus; /* identifier for a update() status*/
E__UPDATESTATUS lasr_updatestatus; /* previous updatestatus */
E__INITSTATUS initstatus; /* identifier for a init() errors*/
E LOGICAL undoable; ;'* can last e,update be undone ? */
ENBOILER;
const E BOILER *e undefined boiler(void)
E INITSTATUS a init(E BOILER *blr);
double a afeedZE BONER *blr);
double e-bfeed(E BOILER *blr);
E UPDATE3'TATUS a update(E BOILER *blr);
E LOGICAL a undo~E BOILER *blr);
double e-hmp2po4(double hmp);
double e~o4model(E BOILER *blr);
double e~hmodel(E BOILER *blr);
double a nasetpoint(E BOILER *blrl;
double e-congruenoyratio(E BOILER *blr);
double e-fixNAN(doub:Le num5er, double forNAN);

CA 02415685 2003-02-04
/* ma"~,c4.c: module f:or model based coordinated phosphate/ph control */
/*
April 4, 1994 added lines near the end of e_update() to force
feed rates within specified pumping lower and upper bounds.
Roundoff error sometimes resulted in pump rates of -l.Oe-10
rather than 0.0; such negative numbers can often spook equipment
which is sometimes set up to ignore such "meaningless" values - JCG
April 10, 1994: fixed a parentheses error near the bottom of
a init(). Error was harmless for most usages but not for
tfie way I was using a init() in MACC4SSP. Error was on
line the began blr->b3 = fixNAN(...).
Also removed two local variables that were never used in
a update() - JCG
April 17th- fixed default blowdown calculation at bottom for e-init()
- JCG same line as bugfix above.
May 2, 1994 - set updatestatus field to E UPDATEOK in a init()
(so it is in a well defined state after each a init() call).
May 29, 1994 - added support for an ammonia correction to the
;pH as per Scot Boyette's request.
~7une 3, 1994 - added an a congruencyratio() exported function
to support callers who warxt to see the Na:P04 ratio of the
entered pH, P04, NH3, and temperature last entered.
*/
#include "macc4.h"
#include <math.h>
/* These macros provide for on--the-fly units conversion from
engineering units to the research units used in MACC4's internal
calculations. Rather than bl.r->famin we write FAMIN(blr), and these
macros guarantee that the resulting value will be in research units,
even though the 'value stored in blr->famin is in engineering units.
*/
# define E LITERS_PER_GALLON (3.785)
# define E MW P04 (94 97)
# define E MW NH3 (17.0)
# define E ZERO K IN F (-459.6.')
# define E GPD TO KG~ERHR(gpd,spg) ((E LITERS_PER GALLON/24.0)*(spg)*(gpd))
# define E PPM TOlMOLESPERKG(ppm,mw) (Tppm)*1 Oe-3/(mw))
# def ine E-I~IOL~,SP~RKG TO PPM (mpk, mw) ( (mpk) *1 . Oe+3* (mw) )
# define E PERCENT 'TO MO~ESPERKG(percent,mw) ((percent)*l.Oe+1/(mw))
# define E LB TO K~(lb) ((lb)*0.45359)
# define E KG TOrLB(kg) ((kg)/0.45359)
# de f ine E F2I~EL~IN ( f ) ( ( 5 . 0 / 9 . 0 ) * ( ( f ) - E ZERO K IN F ) )
# define K DEFAULT 'TEMP 298.1a /*room temperature in degrees K */
# define K DEFAULT NH3 0.0 /*for Na:P04 ratio setpoints, in moles/kg */
# define K DEFAULT :MHO 0.997047 ,/*room temperature density of saturated
H20*/

CA 02415685 2003-02-04
#i. E_ENGINEERING U7VITS ;'* this compilation switch is set in macc4.h */
/*c -~ rersions internal
require to obta.i.n units
MACC4' s : */


/*t~~ws: gallons/day -_> kg/hr */


/*P04 boiler cons:ppm as ortho P04 moles/kg */
-_>


/*P04 fwws conc: % as ortho P04 -_> moles/kg */


/*Boiler mass: lbs -_> kgs */


# define FAMIN(blr) E GPD TO KGPERHR( (blr) ->famin, (blr) ->spga)
# define FAMAX(blr) E_GPD_T0~_KGPERHR((blr)->famax,(blr)->spga)
# define FADEF(blr) E GPD TO~KGPERHR((blr)->fadef,(blr)->spga)
# define P04A(blr) E PERCENT TO MOLESPERKG((blr)->po4a, E MW P04)
# define NAA(blr) ((bIr)->ratioa*P04A(blr))
# define FBMIN(blr) E GPD TO KGPERHR((blr)->fbmin,(blr)->spgb)
# define FBMAX(blr) E GPD_TO~KGPERHR((blr)->fbmax,(blr)->spgb)
# def ine FBDEF (blr) E GPD TO--~KGPERHR ( (blr I - >fbdef , (blr) ->spgb)
# define P04B(blr) E PERCENT TO MOLESPERKG((blr)->po4b, E MW P04)
# define NAB (blr) ( ~;blr> ->rar.:iob*P048 (blr;l ) " -
# define FDT(blr) (iblr)->fdt)
# define USERP04(po9:) E MOLE:~PERKG TO PPM(po4, E MW P04)
# define P04(blr) E PPMrTO MOLESPE~KG~(blr)->po4, E MW P04)
# define LASTP04(blr) E PPM ";'O MOLESPERKG((blr)->lastpo4, E MW_P04)
# define DP04 (blr) E:_PPI~ TO~MOLESPERKG( (blr) ->dpo4, E MW P04)
# define MINP04(blr) E PPM 1C MOLESPERKG((blr)->minpo4, E MW P04)
# define MAXP04(blr) E PPM TO'MOLESPERKG(Cblr)->maxpo4, E MW P04)
# define P04SETPOINT(bIr) E_PPM_TO MOLESPERKG((blr)->po4setpoint, E MW P04)
# def ine NH3 (blr) \ -
E PPM TO MOLESPER.R:G(e fixNAN((blr)->nh3,K DEFAULT NH3), E MW NH3)
# define LASTNH3(blr) \J - - - -
E PPM TO MOLESPERR:G(e fixNAN((blr)->lastnh3,K DEFAULT NH3), E MW NH3)
# define LASTPH(blr) ((Slr)->lastph) - '-
# define PH (blr) ( (blr) ->ph)
# define DPH(blr) ((blr)->dph)
# define PHSETPOINT(blr) ((blr)->phsetpoint)
# define DPHSETPOINT(blr) ((bl.r)->dphsetpoint)
# define LASTT(blr) ((blr)->lastt)
# define T(blr) ((blr)->t)
# define M (blr) E LH_TO KG ( (bl:r) ->m)
# define BDTEMP(bIr) E F2KELVIN(blr->bdtemp)
# define LASTBDTEMP(blr) E F2KELVIN(blr->lastbdtemp)
# define MAX SAMPLE INTERVAL(blr) ((blr)->max sample interval)
# d~~.fine MAX OUTOFBOX ADJUSTMENT(blr) ((blr)->max outofboxladjustment)
# define BEPS(blr) ((blr)->beps)
# define NALEAKMIN(blr) ((blr)-.>naleakmin)
# define BDMIN(blr) E LB TO KG((blr)->bdmin)
# define NALEAKMAX(blr) T(bIr)->naleakmax)
# define USERHD (bd) E KG TO LB (bd)
# define BDMAX(blr) E LB TO KG((blr)->bdmax)
# define MAX RELATIVE ER~OR~bl:: ) ( ( (blr) ->max relative error) /100.0)
# define BD(Slr) E LH TO KG((blr)->bd)
# d~sfine L(blr) ( (blrT->I)
# define DT (blr, n) ( (blr) ->dt [n] )
# define FA(blr,n) E GPD TO KG1?ERHR( (blr) ->fa[n] , (blr) ~->spga)
# define FB(blr,n) E~GPD-TO_KGPERHR((blr)->fb(n], (blr)->spgb)
# d.. f ine DEFAULT DP04 0 . ~
# define DEFAULT DPH 0.05
# define DEFAULT DPHSETPOINT 0.025
# define DEFAULT MAK OUTOFBOX ~'~DJUSTMENT (24.0*7.0) /* seven days */
# define DEFAULT MAX_RELATIVE~ERROR 20.0 /*20% error allowed by default*/
# define DEFAULT MAX SAMPLE IN'.CERVAL (24.0*7.0)
# define DEFAULT BEPS 1.0e-12

CA 02415685 2003-02-04
#e7.se /* research units */
# de~"ine FAMIN(blr) ( (blr) ->farnin)
# de ~e FAMAX(blr) ( (blr) ->farnax)
# detlne FADEF(blr) ((blr)->fadef)
# define P04A(blr) ((blr)->po4a)
# define NAA(blr) ((blr)->ratioa*P04A(blr))
# define FBMIN(blr) ( (blr) ->fbrnin)
# define FBMAX(blr) ((blr)->fbmax)
# define FBDEF(blr) ((blr)->fbdef)
# define P04B (blr) ( (blr) ->po4b)
# define NAB(blr) ((blr)->ratiob*P04B(blr))
# define FDT (blr) ( (blr) ->fdt )
# define LASTP04(blr) ((blr)->lastpo4)
# d.=_fine USERP04 (po4) (po4)
# define P04 (blr) ( (blr) ->po4)
# define DP04(blr) ((blr)->dpo4)
# define MINP04(blr) ((blr)->minpo4)
# define MAXP04 (blr) ( (blr) ->maxpo4)
# define P04SETPOIN'T(blr) ( (b:Lr) ->po4setpoint)
# define LASTNH3(blr) (e fixNADT((blr)->lastnh3, K DEFAULT NH3))
# define NH3(blr) (e fixNAN((blr)->nh3, K DEFAULT NH3)) -
# define LASTPH(blr)-((blr)->laistph) -
# define PH(blr) ((lolr)->ph)
# define DPH(blr) ((b1r)->dphl
# define PHSETPOINT(:blr) ((blr)->phsetpoint)
# define DPHSETPOINT(blr) ((blr)->dphsetpoint)
# define LASTT(blr) ( (blr) ->laa:~tt)
# define T(blr) ((b:Lr)->t)
# define M(blr) ( (blr) ->m)
# define BDTEMP(blrl (273.15 + (blr)->bdtemp)
# define LASTBDTEMP (:blr) (273 . 1.5+ (blr) ->lastbdtemp)
# define BLRTEMP(blr) (273.15+(blr)->blrtemp)
# define MAX SAMPLE INTERVAL(blr) ((blr)->max sample interval)
# define MAX OUTOFBt3:X ADJUSTMEDtT(blr) ( (blr)->max_outofbox adjustment)
# define BEPS (blr) ( (blr) ->bep:;)
# define NALEAKMIN(b1r) ((blr:1->naleakmin)
# define USERBD(bd) (bd)
# define BDMIN(blr) ((blr)->bdmin)
# define NALEAKMAX(blr) ((blr)-~>naleakmax)
# define BDMAX(blr) ((blr)->bdmax)
# define MAX RELATIVE ERROR(blr) ((blr)->max relative error)
# define BD(blr) ((b1r)->bd) - -
# define L(blr) ((b:Lr)->1)
# define DT (blr, n) ( (blr) ->dt [n] )
# define FA(blr,n) ( (blr) ->fa [n] )
# define FB (blr, n) ( (blr) ->fb [n] )
# define DEFAULT DP04 E_PPM_TO__MOLESPERKG(0.5,E_MW_P04)
# define DEFAULT DPH 0 05
# define DEFAULT DPHSETPOINT 0.025
# define DEFAULT MAX_OUTOFBOX_ADJUSTMENT (24.0*7.0) /* seven days */
# define DEFAULT MAX_RELATIVE~ERROR 0.2 /*20% error allowed by default*/
# de: f ine DEFAULT MAX SAMPLE I'~VTERVAL ( 24 . 0 * 7 . 0 )
# define DEFAULT_BEPS 1.0e-12
#endif
# define ROUNDED DT ( blr, n) (e round down (DT (blr, n) , FDT (blr) ) )

CA 02415685 2003-02-04
/*,Misc. Functions */
dou~~' a round down(double x, double dx) { /* round x down to multiple of dx
*/
re~..rn ( x --( (dx: <= 0.0) :' 0.0 . fmod(x,dx) ) ) ;
double a min(double x, double y) { /* minimum of 2 numbers */
returnZ (x < y) ? x: . y) ;
double a max(double x, double y) { /* maximum of 2 numbers */
returnT(x > y) ? x: : y) ;
double a forceinrang~e(double x, double low, double high) {/*force
low<=x<=high*/
if ( x <= low)
return(low);
else if ( x >= high)
return(high);
else
return(x);
/* 'use fabs (x) for ~ x ~ , log (x) for In (x) */
/* l3asic Vector Operations */
#define VBUF 100
E_VECTOR *e vec(dou.ble po4, double na) { /* convert 2 components to vec*/
static E SECTOR v[VBUF]={0.0}; /* uses a circular buffer; callers must not */
static int ibuf = VBUF-1; ,~* depend upon this memory since is will be */
ibuf = (ibuf+1) % VBUF; /* reused every VBUFth call */
v[ibuf].po4 = po4; ;* (for example, exp ressions with more than */
v[ibuf].na = na; ~* ~Uroa~~eneededl~o work aroundranlMSsC*bu */
r~_turn(&v[ibuf]); ~* When passing structures to/from functions*/
double a dot(E VECTOR *vl, E VECTOR *v2) { /* inner (dot) product of 2 vecs*/
rs~turnZvl->po4*v2->po4 + vl->na*v2->na);
double a norm(E VECTOR *v) { /* magnitude (norm) of vector */
returnZsqrt(e-dot(v,v)));
double a norm2(E VECTOR *v) { /* magnitude (norm) of vector, squared */
returnTe dot(v;v));
/*e sXv: multiply ("X") a scalar, s, tires a vector, v */
E VECTOR *e sXv(double s, E VECTOR *v)
-rE:turn (e vec (s*v->po4, s*v=>na) ) ;
E VECTOR *e vDs(E VECTOR *v, double s) { /* divide a vector by a scalar */
-return(e_sxv(1.~'/s, v) ) ;
%* a vPv: add ("P") two vectors */
E_VECTOR *e vPv(E VECTOR *vl, E~VECTOR *v2)
return(e vec(vl->po4 + v2->po4,v1->na + v2->na));
/* a vSv: Subtract ("S") two vectors (v1 - v2)*/
E VECTOR *e vSv(E VECTOR *vl, 13 VECTOR *v2) {
-return(e vPv(vl, e_sXv(-1.0, v2)));

CA 02415685 2003-02-04
/* e,vEv: Are two vectors approximately equal, within machine precision ? */
E_L0~'~"'~AL a vEv ( E VECTOR *vl , E VECTOR *v2 ) {
re ~n ( Te norm(e_vSv(vl,v2)~ <= DBL,EPSILON*(e norm(vl)+e norm(v2))) ?
E_TRUE . E_FALSE);
~* Vector Functions */
E_VECTOR *e rot90(E VECTOR *v) {/*rotate vector counterclockwise 90 degrees */
return(e_vec(- v->na, v->po4));
/* ~~ between: given three co-l~.near points, is <B> between <A> and <C> ? */
E LOGICAL a between(E VECTOR *vA, E VECTOR. *vB, E VECTOR *vC) {
-return( (e norm(e vSv(vA,vB)) <= a norm(e vSv(vA,vC)) &&
a norm(e~ vSv(vB,vC)) <= e_norm(e vSv(vA,vC))) ? E TRUE . E FALSE);
_ .._ - -
/* a decompose: decomposes a given vector into the given ~~coordinate
system's.
Specifically, computes alpha and beta such that:
<A> _ <Origin> + alpha*<X~:> + beta*<YY>.
Here <A>, <Origin>, <XX>, and <,Y'Y> are vectors, alpha and beta are scalars.
Note: <XX> and <Y'Y> must be linearly independent and non-zero,
or divide by 0 errors will occur (macc4 prevents
such a possibility by various up front checks).
*/
void a decompose(E VECTOR *vA, E VECTOR *vOrigin, E VECTOR *vXX, E-VECTOR
*vYY,
double *alpha, 3ouble *beta)
{ E VECTOR *vx, *vu, *vuperpx, *vYYperpx;
vx = a vDs(vXX,e norm(vXX));
vu = a vSv(vA,vOrigin);
vuperpX = a vSv(vu,, e_sXv(e dot(vu,vx), vx));
vY'Yperpx = a vSv (v'.tY, a sXv~e dot (vYY, vx) , vx) ) ;
*beta = a dot (vupesrpx, vYYpez~px) /e dot (vYYperpx, vYYperpx) ;
*a.lpha.' = a dot(vx, a vSv(vu,, e-sXv(*beta,vYY)))/e norm(vXX);.

CA 02415685 2003-02-04
/* =unctions for ph to no Conversion */
/* d'-who: density of water a:Long the saturation line ("psat") */
/* (i...:luded from CMS 2.0 module db2.e) */
/* Scott Boyette provided; original source Journal of Physical and
Chemical Reference Data, Vol_ 10, No. 2, 1981 p 295. W. L. Marshal
and E. U. Franck.
*/
double db rho(double t) { /* t in degrees K */
static double a[] -.{-0.735396, 0.015695, -1.153587E-5,
-8.157489E-8, 2.121698E-10, -1.590221E-13
int i = sizeof(a)/sizeof(a[o]);
double rho = a [--i] ; /* ~.:c>mpute "sum over i of a [i] *t**i" */
if (fobs (K DEFATJLT TEMP-t;~ r= K DEFAULT TEMP*DBL EPSILON)
- - /* to avoid"fit of a fit" errors */
return(K DEF.~ULT_RHO); /* for special case of room temp values*/
- ,~* use the exact value (as per S. Boyette)*/
do
rho *= t;
rho += af--i.l;
while (i > 0),;
rho /_ (1.0 + 0.003582*t);
return(rho);
/* e: no: Back calculates an "equivalent no" in the boiler, using,
tfie charge balance equation and measured po4 and ph in the boiler.
po4, no concentrations are in moles/kg.-Temperature in Kelvin. Activity is
assumed 1.0 for simplicity (no ionic strength correction).
See CMS (Condensat:e Modell.irag System) 2.0 module db2.c for equilibrium
constants and the algebraic expression used for the charge balance equation.
May 29, 1994: added nh3 parameter to account for ammonia
*/
double a na(double po4, double ph, double t, double nh3) {
double-Ti - 1.0/t;
double rho = db rho(t);
double H = pow(10.0, -ph)/rho;
double Kl - pow(10.0, - (-3.900391 + 0.01216*t + 725.804792/t));
double K2 - pow(10.0, - (-2.654013+0.01534*t+1579.171467/t));
double K3 - pow(10.0, - (-3.399999E-3*t + 12.44871));
double Kw - pow (:10.0,
- 4.098 + Ti*(-3245.2 + Ti*(2.2362e5 - Ti*3.984e7)) +
1og10(rho)*(13.957 + Ti*(-1262,3 + Ti*8.5641e5)));
double Klnh3 - pow(10.0, - (1.2.423779 + 6.202054E-4*t +
2188.435335/t - 4,325705*1og10(t))):
return(Kw/H - H - nh3*(H/Klnh3)/(1.0 + H/Klnh3) +
+ po4* (K1/H +. 2.0* (Kl/H) * (K2/H) + 3.0* (K1/H) * (K2/H) * (K3/H) ) /
(1.0 + (K1/H) + (Kl/H)*(K2/H) + (Kl/H)*(K2/H)*(K3/H)));
/*e~congruencyratio: exported congruency Na:P04 ratio calculation */
double a congruencyr~atio (E BOILER *blr)
if_ (E 1~AN =- blr->po4

CA 02415685 2003-02-04
0 . 0 >= P04 (b1_ ) ~ i
E NAN =_ blr--:>ph )
~,eturn(E NAN);
..aturn(e na(P04 (blr) ,
E'
PH (blr) ,
e_~:LxNAN(BDTEMP(b1r), K DEFAULT TEMP)
a f:LxNAN(NH3(blr), K DEFAULT NH3)
> 7 1?04 (blr)

CA 02415685 2003-02-04
double a napo4ratio(~ BOILER *blr) { /* napo4ratio setpoint for boiler */
' return ((blr->napo4ratio != E NAN) ? (blr->napo4ratio) .
T.',e na(P04SETPOINT(blr), F~HSETPOINT(blr), K DEFAULT TEMP, K DEFAULT NH3)/
~04SETPOINT(blr)) );
double a minnapo4rat.io(E BOILER. *blr) { /*min napo4ratio setpoint for boiler
*/
return ((blr->minnapo4ratio != E NAN) ? (blr->minnapo4ratio) .
a max(E MINMINNAP04RATI0,
a naZP04SETPOINT(blr),PHSETPOINT(blr)-DPHSETPOINT(blr),
K DEFAULT 'TEMP, K DEFAULT_NH3)
P04SETPOINT(b:Lr)) )
double a maxnapo4rat:..io (E BOILER. *blr) { /*max napo4ratio setpoint for
boiler */
return ((blr->maxnapo4ratio != E NAN) ? (blr->maxnapo4ratio) .
e__m i n ( E_MAxMAXNAP04 RAT I O ,
a na(P04SETI?DINT(blr),FHSETPOINT(blr)+DPHSETPOINT(blr),
K DEFAULT TEMP,K DEFAULT_NH3)
/ P04SETPOINT (b:LrT) ) ; -
/* e_fixNAN: replace E NAN's (not a number) with a given value */
double a fixNAN(doub:Le number, double forNAN) {
return (number =- 3~ NAN ? forNAN . number) ;

CA 02415685 2003-02-04
/* Main Functions */
/*~ '.nit: check that required constants of macc4 are properly specified*/
/* .~.~ould be used in conjunction with a undefined boiler() as defined in
macc4.h, e.g..
# include "macc4.h"
E BOILER b1 = *e undefined boiler(); (sets all fields to E NAN (undefined)
bl.famin = fam.in; bl.famaX = ... etc. (define the fields)-
if (e init(&bl) != E_INITC)K)
error ( ) ;
*/
E_INITSTATUS e_init(E_BOILER *blr)
b:Lr->initstatus = E INITOK; /* clear initstatus flag */
blr->undoable = E FALSE;
blr->updatestatus = E UPDATEOK;
i:E (blr->t =.- E NAN) blr->initstatus = E MISSING T;
else if (blr->famin =- E NAN) blr->initstatus = E MISSING FAMIN;
else if (blr->famax =- ErNAN) blr->initstatus = E MISSING FAMAX;
e:Lse if (blr->fadef =- E~NAN) blr->initstatus = E MISSING FADEF;
a:Lse if (blr->po4a == E NAN) blr->initstatus = E MISSING P04A;
else if (blr->ratioa== E NAN) blr->initstatus = E MISSING RATIOA;
else if (blr->fbmin =- E~NAN) blr->initstatus = E MISSING FBMIN;
a:Lse if (blr->fbmax =- E'-NAN) blr->initstatus = E MISSING FBMAX;
else if (blr->fbdef =- E NAN) blr->initstatus = E MISSING FBDEF;
else if (blr->po4b -- E NAN) blr->initstatus = E MISSINGrP04B;
else if (blr->ratiob== E_NAN) blr->initstatus = E MISSING RATIOB;
a:Lse if (blr->po4 -- E NAN) blr->initstatus = E MISSING P04;
else if (blr->ph -- E NAN) blr->initstatus = E MISSING~PH;
else if (blr->m -- E NAN) blr->initstatus = E MISSING M;
a:Lse if (blr->bdtemp== E NAN) blr->initstatus = E-MISSING BDTEMP;
a:Lse if (blr->po4setpoint -
-- E NANI blr->initstatus = E MISSING P04SETPOINT;
else if (blr->minpo4== E_NAN) blr->initstatus = E MISSING~MINP04;
a:Lse if (blr->maxpo4== E NAN) blr->in:itstatus = E_MISSING MAXP04;
else if (blr->phsetpoint-
-- E NAN &&
blr->napo4ratio
-=E_NAN) blr->initstatus = E MISSING RATIOSETPOINT;
#if E ENGINEERING UNITS
else if (blr->spga =- E NAN) blr->initstatus = E MISSING SPGA;
e:Lse if (blr->spgb =- E_NAN) blr->initstatus = E'MISSING SPGB;
#endi f

CA 02415685 2003-02-04
else { /* if we got this far, all required fields rave been specified */
/*,-~~~fine initial feed program as feeding the default feed rates */
blr->dt [0] - blr->dt [1] _ t) ., 0;
blr->fa [0] - blr->fa [1] - blr->fa [2] - blr->fadef ;
blr->fb [0] - blr->fb [1] .- blr->fb [2] - blr->fbdef;
blr->lastt = bl.r->t; /* set origin of time, po4, and ph */
blr->lastbdtemp = blr->bdte:mp;
blr->lastpo4 = blr->po4;
blr->lastnh3 = :olr->nh3;
blr->lastph = blr->ph;
/* provide defaults for the optional parameters if they are undefined */
blr->fdt = a fix:L~AN(blr->fdt, 0.0); I*assume instantaneous pump changes*/
blr->dpo4 = a fixNAN(blr->dpo4, DEFAULT DP04);
blr->dph = a ~i:K~lAN(blr->dph, DEFAULT DPH) ;
blr->dphsetpoint = a fixNAN(blr->dphsetpoint, DEFAULT DPHSETPOINT);
blr->max sample interval = a fixNAN(blr->max_sample~interval,
DEFAUL3' MAX S~.'~IPLE INTERVAL) ;
blr->max outo~bo:x adjustment =
a fixNAN(blr-:>max outofbox adjustment, DEFAULT MAX-OUTOFBOX ADJUSTMENT);
blr->beps = a fi:xNAN(blr->beps, DEFAULT HEPS);
blr->naleakmaX := a fixNAN(blr->naleakmax,
1.0e3 * (FAMAX~blr)*fabs(NAA(blr)) + FBMAX(blr)*fabs(NAB(blr)) +
FAM,AX(blr)*fabs(PO4A(blr)) + FBMAX(blr)*fabs(P04B(blr))));
blr->naleakmin = a fixNAN(blr->naleakmin, - blr->naleakmax);
blr->bdmax = a :'ix~lAN(blr~->bdmax, blr->m/1.0) ;
/*default bdmax empties boiler in 1 hr*/
blr->bdmin = a ~ixNAN(blr~->bdmin, l.Oe-6*blr->m/1.0);
/* defaultf bdmin is blowdown rate needed for a time constant of 1e6 hours */
blr->max relatives error = a fixNAN(blr->max relative error,
- - DEFAUL2 MAX RELATIVE ERROR);

CA 02415685 2003-02-04
/* apply consistency checks to the constants of the macc4 model */
if (HDMAX(blr) < BDMIN(blr))
,* blr->initstatus = E BDMAX LT BDMIN;
lse if (NALEAKMAX(blr) < DJALE~KMIN(blr))
blr->initstatus = E NALEAKMAX LT NALEAKMIN;
else if (FAMAX(blr) --FAMIDT(blrj <=
DBL EPSILON * (fabs (FAMAX (blr) ) +fabs (FAMIN (blr) ) ) )
blr->initstatus = E_FAMA~; LE FAMIN; j* max pump rates less than min */
else if (FBMAX(blr) - FBMIDJTblr) <_
DBL EPSILON*fabs(fabs(F'BMAX(blr))+fabs(FBMIN(blr))))
blr->initstatus.= E FBM~, LE FBMIN; /* max pump rates less than min */
else if (MINP04(blr), < E MI:NMINP04)
blr->initstatus = E MINP04 LT MINMINP04;
else if (MAXP04 (blr) > E W~XMAXP04)
blr->initstatus = E MAXP04 GT MAXMAXP04;
else if (P04SET:POINT(blr) <: MINP04 (blr) )
blr->initstatus = E P04SE:TPOINT LT MINP04;
else if (P04SETPOINT(blr) , MAXP04(bIr))
blr->initstatus = E P04SF;TPOINT GT MAXP04;
else if (P04A(b:Lr) < 0.0)
blr->initstatus = E P04A LT ZERO;
else if (P04B (b:Lr) < ~. 0) --
blr->initstatus = E P04B LT_ZERO;
else if (e minnapo4ratio(b7.r) < E MINMINNAP04RATI0)
blr->initstatus = E MINRATIO LT_MINMINRATIO;
else if (e maxna:po4ratio (b7.r) > E MAXMAXNAP04RATI0)
blr->initstatus = E MAXRATIO_GT MAXMAXRATIO;
else if (e napo4ratioTblr) < a minnapo4ratio(blri)
blr->initstatus = E RATIO LT'_MINRATIO;
else if (e napo4ratioTblr) :> a maxnapo4ratio(blr))
blr->initstatus = E RATIO GT MAXRATIO;
else if (fabs(NAA(blr~*P04BTblr) - NAB(blr)*P04A(blr)) <_
DBL EPSILON * (fabs (NAA(blr) *P04B (blr) ) ~ fabs (NAB (blr) *P04A(blr) ) ) )
blr->initstatus = E RATIOA EQ RATIOB; /* both tanks have same na/po4 rati
else {/*initial blowdown, 7.eak only used for testing--reasonable defaults*/
blr->1 = a fix~lAN(blr->:1, 0.0);
blr->bd = a f.ixNAN(blr->bd,
USERBD((FADEF(blrT*:P04A(blr) + FBDEF(blr)*P04B(blr))/P04SETPOINT(blr)));
re=turn(blr->initstatus);

CA 02415685 2003-02-04
/* e~_getfeed: retrieves feed rates for pumps a or b for a given time */
doui ' e_getfeed(E BOILER *blr, double *f) {
double deltat :- T (blr) - L~ASTT (blr) ;
if (deltat < RO1?NDED_DT(blr,0))/*use appropriate part of feed program */
return(f [0] ) ;
else if (deltat < ROUNDED_DT(blr,0) + ROUNDED_DT(blr,l))
return(f [1] ) ,;
else
return(f [2] ) ;
double a afeed(E BONER *blr) { /* retrieves pump a feed rate */
returnZe getfeed(b:Lr, blr->fa));
double a bfeed(E BOILER *blr) { /* retrieves pump b feed rate */
re:turnTe getfeed (b:Lr, blr->:Eb) ) ;
/*e-rpo4pred: predicted po4 concentration */
double eJpo4pred(E BOILER *blr) {
double po4 == i~P.STP04 (blr) ;
double deltat::ime = T (blr) - LASTT (blr) ;
int i = 0;
double deltat[3];
deltat[0] - e__min(deltatime,ROUNDED DT(blr,0));
deltat [1] - a min (deltatime - deltat (O] , ROUNDED DT (blr, 1) ) ;
deltat [2] - di~ltatime ~- deltat [0] - deltat [1] ;
for (i = 0; i < 3; i++) {
po4 = ( (FA(blr, i) *PO~A~blr) +FB (blr, i> *P04B (blr) ) /BD (blr) )
(1.0-exp(- deltat [i] *BD(blr) /M(blr) ) ) +
po4*exp ( - deltat [i] *BD (blr) /M (blr) ) ;
}eturn(po4);

CA 02415685 2003-02-04
/* F~ estimate bd: estimate blowdown (B) via bisection */
dour ~ e_estimate bd(E BOILER *blr) {
int bisections :- E MAXBISECTIONS;
double Blow = blr=>bdmin;
double Bhigh = blr->bdmax;
blr->bd = 0.5 * (Blow + Bhigh);
while (bisections-- && (Bh.igh - Blow) >
blr->bep:a* (fabs (blr->bdmax) + fabs (blr->bdmin) ) ) {
if (e~ o4pred(blr) > P04(blr))
Blow = bl:~->bd; !* estimated po4 too high - Blowdown too low */
else
Bhigh = b:lr->bd; %* estimated po4 too low - Blowdown too high */
blr->bd = O.a * (Blow + Bhigh);
/* f:lag if the final. low, high don't actually bracket the root */
blr->bd = Blow;
if (e po4pred(bar) < P04(blr)) blr->updatestatus ~= E_INCONSISTENT-P04;
blr- >Sd = Bhigh ;
if (e_po4pred(b:Lr) > P04(blr)) blr->updatestatus ~= E'INCONSISTENT-P04;
return (blr->bd :- 0 . 5 * (~3low + Bhigh) ) ;
/*e~napred: predicted na concentration */
doux>le a napred(E BO:CLER *blr) {
double na == e: na(LASTP04(blr), LASTPH(blr), LASTBDTEMP(blr),
LASTNH3(blr));
double deltatime = T(blr) - LASTT(blr);
int i = o;
double deltat[3];
deltat [0] - e__min (deltatime, ROUNDED DT (blr, 0 ) ) ;
deltat (1] - a min (deltatime - deltat (0] , ROUNDED~DT (blr, 1) ) ;
deltat [2] = dEeltatime - deltat ~O] -~ deltat (1] ;
for (i=0; i < 3; i++) (,
na = ( (FA(b:Lr, i) *NAAi,b~lr) + FB (blr, i) *NAB (blr) +L (blr) ) /BD (blr)
)
(1.0-exp( - deltat(i]*BD(blr)/M(blr))) +
na*exp ( - dE~ltat [i] *BD (blr) /M (blr) ) ;
return(na);

CA 02415685 2003-02-04
/* = estimate_1: estimate na "leak" (L) via bisection */
don ' a estimate 1 (E BOILER *blr)
int bisections = E MAXBISECTI01VS;
double na = a na(P04(blr), PH(blr), BDTEMP(blr), NH3(blr));
double Llow = bIr->naleakmin;
double thigh = blr->naleakmax;
blr->1 = 0.5*(Llow+Lhigh);
while (bisections-- && (thigh - Llow) >
blr->beps* (fabs (blr->naleakmax) + fabs (blr->naleakmin) ) ) {
if (e napred(blr) > na) /* estimated na too high - na Leak too high */
thigh = blr->1;
else /* estimated na too low - na leak too low */
Llow = blr->1;
blr->1 = 0.5*(Llow+Lhigh);
/* flag if the final low, high don't actually bracket the root */
blr->1 = Llow;
if (e napred(blr) > na) blr->updatestatus ~= E-INCONSISTENT_PH;
blr->I = thigh;
if (e napred(blr) < na) bl.r->updatestatus ~= E_INCONSISTENT_PH;
return(blr->1 = 0.5*(Llcw+Lhigh));
/* e~phmodel: computes the current model predicted pH for given boiler.*/
#define LOGBASE20FRELATIVEPHERROR 64 /*works out to less than 1e-16 pH units*/
double e~hmodel(E BOILER *blr) {
double minph = 1.0;
double maxph = 14.0;
double ph = 0.5*(minph + maxph);
double po4 = e_po4pred(blr);
double na - e,napred(blr);
int i = 0;
for (i=0; i < LOGBASE20FRELATIVEPHERROR-; i++) {
if (e na(po4,ph,BDTEMP(blr), NH3(blr)) > na)
maxph = ph;
else
minph = ph;
~ ph = 0.5*(minph + maxph);
return(ph);
/*e_po4model: computes the current model predicted po4 for given boiler*/
double a po4model(E BOILER *blr) {
return (USERP04 (e~o4pred(b:lr) ) ) ;

CA 02415685 2003-02-04
/* Note on our repre:sentatior~ of convex polygon regions:
. ~nvex polygon regions with .N vertexesr p1, p2, ... pn are represented by
. 1 points, R [0] , R [1] , . .. . , R [N] as follows :
R[0] - p1; F.[1] - p2; .... R[N-1] - pn; R[N] - p1;
Here pl,p2; p2,p3; ... pn,pl form the edges of the convex polygon region.
We pass the array, R and number of vertexes, N, when passing a
convex polygon region to a function; note that it takes an array of
N+1 points to represent a convex polygon region with N distinct
vertexes. (You can think of the last point as a delimeter, just like
the 0 at the end of strings). */
/* a perimetercrossings: determines points where a given line
intersects the perimeter c~f .a given convex polygon region; returns
number of intersection points */
int e_perimetercrossings(E_VECTOR *vX, /* 2 element array of intersection
pts*/
E VECTOR *vV, /* V [i] , V [i+1] are edges; V [NV] ==V [0] */
int NV, /* number of vertexes in region */
E VEC'rc~R *vPl,/* line that (may) intersect region */
E~_VECTOR *vP2)/* P1 <> P2 is required ! */
{ int i = 0, N = 0;
double lastalpha, lastbeta, alpha, beta;
a decompose(&vV[0], vPl, a vSv(vP2,vP1), e-rot90(e,vSv(vP2,vP1)),
&lastalpha, &lastbeta);
for (i=0; i < NV; i++)
a decompose(&vV[i+1],{vP7., a vSv(vP2,vP1), a rot90(e vSv(vP2,vP1)),
&alpha, &beta);
if (lastbeta*beta <= 0.0 && /* does edge crosses given line ? */
(fabs(lastbeta) > 0.0 ~~ fabs(beta) > 0.0 ) ) {
E VECTOR *X = a vPv(&vV[i],
a sXv(fabs(lastbeta)/(fabs(lastbeta)+fabs(beta)),
a vSv (&vV [i+1] , &vV [i] ) ) ) ;
if (N
vX [N++] - *X.;
else if (e between(&vXl:l;l, ~vX[OJ, X))
vX[0] _ *X;
else if (e between ( &vX I:0] , &vX [1] , X) )
vX [1] _ *X;
/* else first 2 intersection points are already the furthest apart */
la}tbeta = beta;
eturn (N) ;

CA 02415685 2003-02-04
/* a within~region: is the given point within (or on perimeter of) region ? */
E_l-~ICAL a within oegion(E_VECTOR *vP, E-VECTOR *vR, int N)
ECTOR vX [ 2 ] ;
it ( a vEv (vP, &vR [0] ) )
return(E TRUE);
else if (e_perimet:ercrossings(vX, vR, N, vP, &vR[0]) > 1 &&
E TRUE ~_= e_between (&vX [0] , vP, &vX [1] ) )
return(E_TRUE);
else
return(E_FALSE);
/*
a clip_region: clips the part of the convex polygon region, R,
tFlat is to your left if you are standing at P1 and walking towards P2
(or walking backwards from P2 towards P1). Assumes P1 <> P2.
*/
int a clip_region(E VECTOR'*vC, /* clipped region (output) */
- E-VECTOR *vR, /* original region (input) */
in.t N, /* number of distinct points on perimeter */
E VECTOR *vPl, E VECTOR *vP2) { /* clipping line */
double alpha, beta, lastbet:a;
int i = 0, j - 0;
a decompose(&vR[0], vPl, a vSv(vP2, vPl), e,rot90(e vSv(vP2, vPl)),
&alpha, &beta);
for (i=0; i < N; i++)
if (beta <= 0) /* to{the right of, or on, the clipping line through P1,P2*/
vC [j++] - vR [i] ;
lastbeta = beta;
a decompose(&vR[i+1], vPl, a vSv(vP2,vP1), a rot90(e,vSv(vP2, vP1)),
- &al:pha, &beta);
if (lastbeta*beta < 0.0) /* Segment R[i],R[i+1] crosses clipping line: */
vC[j++] - *e vPv(&vR[i], /*add point where it crosses to clipped region*/
- e_sXv (fabs (lastbeta) / (fabs (lastbeta) +fabs (beta) ) ,
a vSv(&vR[i+1] , &vR[i] ) ) ) ;
vc~[j] - vC[0]; l* "close the loop" to form the convex polygon region */
return (j);

CA 02415685 2003-02-04
e_intersect regions: determines the region, AB, which is the
'~'tersection of the two given convex polygon regions, A and B. The
_~rst region, A, must be oriented such that moving from vertex A[i]
to vertex A[i+1] places the outside of the region to one's left (in
other words, you are moving clockwise around the perimeter).
/*
IMPORTANT: AB(] MUST contain at least NA+NB+1 elements, which is
the maximum possible number of points needed to define the region of
intersection, or memory will be trashed. Function returns the number
of distinct vertexes in the resulting, intersecting, region.
For more information, see section 3,:14.1, "The Sutherland-Hodgman
Polygon-Clipping Algorithm", of the second edition of Foley, et. al's
"Computer Graphics" 1990, Addison-Wesley.
*/
int e_intersect-regions(E VECTOR *vAH, /* intersecting region (returns NAB) */
E VECTOR *vA, /* points representing the 1st region */
int NA, /* number of vertexes in 1st region */
E VECTOR *vB, i* points representing the 2nd region */
int NBA { /* number of vertexes in 2nd region */
int l = 0, j - 0, N = NB;
for (l = 0; l < NA; i++)
for (j = N; j >= 0; j--) { /* copy/move region to be clipped to end of AB*/
vAB (NA+NB-N+j ] - (i==0) ? vB [j ] . vAB [j ] ; /*AB [] MUST have NA+NB+1
elems*/
N = a clip-region(vAB, vAB+NA+NB-N, N, &vA[i], &vA[i+1]);
return (N) ;
/* e-point2chord: return point on the chord (line-segment) closest to P */
E VECTOR *e~point2chord(E VECTOR *vP, E VECTOR *vLl, E VECTOR *vL2)
i:E (e vEv(vLl, vL2))
return(vLl);
e:Lse {
double alpha, beta;
a decompose(vP, vLl, a vSv(vL2, vLl), a rot90(e vSv(vL2, vLl)),
&alpha, &beta);
return(e vPv(vLl, a sXv(e max(0.0, a min(1.0, alpha)), a vSvl.vL2, vLl))));
- _ _ _ -

CA 02415685 2003-02-04
/* a region2region: given 2 non-intersecting convex polygon regions, returns
tfie point on region A that is closest to (the perimeter of) region B */
E_v _TOR *e region2re~ion(E VECTOR *vA, int NA, E VECTOR *vB, int NB) {
E VECTOR vminA = vA 0], vminB = vB[0], vX;
int i = 0, j - 0;
for (i=0; i < NA; i++) {
for (j = 0; j < NB; j++) {
vX = *e_point2chord(&vA[i] , &vB[j] , &vB[j+1] ) ;
i f ( a norm ( a vSv ( &vA [ i ] , &vX ) ) < a norm ( e,vSv ( &vminA, &vminB )
) )
vminA = vATi ] ;
vminB = vX;
for (i=0; i < NB; i++)
< NA; j++) {
fovX(~ *e~oint2chord(&vB Li] , &vA[j] , &vA[j+ll ) ;
if (a norm(a vSv(&vX, &vB[i])) < a norm(a vsv(&vminA, &vminB)>) {
vminA = vX;
vminB = vH [i] ;
}~~turn(e vec(vminA.po4,vminA.na));
# define E REPS 5 ;* number of replicates for sensitivity analysis */
/* ce_pertubate: perturbs field in boiler structure for sensitivity analysis
*/
void e_perturbat{(E BOILER *blr, int index, double multiplier)
switch(index)
case 0: blr->lastpo4 +_ (multiplier*b1r->dpo4);
break;
case 1: blr->lastph +_ (multiplier*blr->dph);
break;
case 2: blr->po4 +_ (multiplier*blr->dpo4);
break;
case 3: blr->ph +_ (multiplier*blr->dph);
break;
default: break;

CA 02415685 2003-02-04
/* a backup: stores E BOILER'S current state for later use by a undo */
vor °_ backup(E BOILER *blr) {
i.__ i = 0;
for (i=0; i < 2; i++) {lr->lastdt [i] - blr->dt [i] ;
for (i=0; i < 3; i++)
blr->lastfa [i] - blr->fa [i] ;
blr->lastfb [i] - blr->fb [i] ;
blr->b4lastt - blr->lastt;
b:Lr->b4lastbdtemp =.blr->lastbdtemp;
b:Lr->b4lastpo4 - blr->lastpo4;
b:Lr->b4lastnh3 - blr->lastnh3;
blr->b4lastph - blr->lastph;
b:Lr->lastbd - blr->bd;
b:Lr->lastl - blr->l;
for (i=0; i < E NV+1; i++) blr->vlastV [i] - blr->vV [i] ;
for (i=0; i < E NW+1; i++) blr->vlastW[i] - blr->vW(i];
for ( i=0; i < 3 ; i++) blr->vlastF [i] - blr->vF [i] ;
b:Lr->lastupdatestatus = blr->updatestatus;
blr->undoable = E TRUE; /* since we are backed up, an undo is now possible */
%* s: undo: undoes the effect of the last a update(); 1 update can be undone.
*/
E LOGICAL a undo(E BOILER *blr) {
-if (E FALSE =- bIr->undoable)
return(E FALSE);
e:Lse {
int i = 0;
for (i=0; i < 2; i++) blr->dt [i] - blr->lastdt [i] ;
for (i = 0; i < 3; i++)
blr->fa(i] - blr->last~a[i];
blr->fb [i] - blr->lastfb [i] ;
blr->lastt - blr->b4lastt;


blr->lastbdtemp- blr->b4lastbdtemp;


blr->lastpo4 - blr->b4lastpo4;


blr->lastnh3 - blr->b4lastnh3;


blr->lastph - blr->b4lastph;


blr->bd - blr->lastbd;


blr->1 = blr->lastl;


for (i=0; i NV+1; i++) blr->vV[i] - blr->vlastV(i];
< E


for (i=0; i NW+1; i++) blr->vW[i] - blr->vlastW[i];
< E~


for (i=0; i i++) blr->vF[i] - blr->vlastF[i];
< 3;


blr->updatestatus
= blr->lastupdatestatus;



blr->undoable = E_FALSE; ,!* no more than 1 undo between updates */
return(E TRUE);

CA 02415685 2003-02-04
/* E~ update: update the feed program associated with a boiler */
E_L" ~.TESTATUS a update(E BOILER *blr)
1
int l = 0, i0 = 0, NV = E NV, NW = E NW, NVW = 0;
E__VECTOR vfamin, vfamax, vfbmin, vfbmax, vL;
E_VECTOR *vV=blr->vV, vminV, *vW=blr->vW, vminW, vVW[E NV+E NW+1];
E__VECTOR vP, vT, uSS;
E VECTOR *vF = blr->vF, vX[2];
double fa [E REPS] , fb [E REPS] ;
double ssq = 0.0;
a backup(blr); /* backup the current state for undo purposes */
b'Ir->updatestatus = E UPDATEOK; /* clear any previous status */
/*define the target wedge (the "box" in coordinated phosphate/ph lingo)... */
/*N.B: the region described by vW must be oriented in
t:ae clockwise direction, c.f. comment at top of e_intersect regions() */
v1N[0] - *e vec(MINP04(blr), MINP04(blr)*e minnapo4ratio(bIr));
vW [1] - *e vec(MINP04(blr), MINP04(blr)*e maxnapo4ratio(blr));
vW [2] - *e vec (MAXP04 (blr) ,. MAXP04 (blr) *e maxnapo4ratio (blr) ) ;
viN[3] - *e vec(MAXP04(blr), MAXP04(blr)*e minnapo4ratio(blr));
vw [4l - ~wTo] ;
/* ... and target point */
v'T = *e vec(P04SETPOINT(blr)" P04SETPOINT(blr)*e napo4ratio(blr));
/* ;estimate the steady-state boiler concentration variability due to */
/* v~he given variability of P04 (dpo4) and pH (dph), via a propagation */
/* of errors calculation. We assume that the impacts of the errors in */
/* lastpo4, lastph, po4, and ph on steady state boiler concentrations */
/* .are independent; since this is not strictly true, the calculation */
/* below will tend to overestimate the resulting error on steady state */
/* ;toiler concentrations. But :it should be a reasonable estimate. */
i0 = ((DP04(blr)<=0.0 && DPH(blr) <= 0.0) ? (E REPS-1) ~ 0);
for (l = i0; l < E REPS; e~perturbate(blr, i++; -1.0))
/* this loop evaluates steady-state feed rates using pertubated inputs */
a perturbate(blr, l, 1.0); /* varies one of: lastpo4,lastph,po4,ph *~
bIr->bd = e_estimate_bd(blr); /* (if l = E_REPS-i then it varies nothing)*i
blr->1 - a estimate 1(blr>;
vfamin = *e vet (FAMIN (bl.r) *P04A(blr) ; BD (blr) ,
FAMIN(bl.r) *NAA(blr) /BD(blr) ) ;
vfbmin = *e_vet(FBMIN(bl.r)*P04B(blr)/BD(blr),
FHMIN (bl.r) *NAB (blr) /BD (blr) ) ;
vfamax = *e vet (FAMAX (blr) *P04A (blr) /BD (blr) ,
FAMAX (b~..r) *NAA (blr) /BD (blr) ) ;
vfbmax = *e vet (FBMAX (b7~.r) *P04B (blr) /BD (blr) ,
- FBMAX (b'_.r) *NAB (blr> /BD (blr) ) ;
vL - *e vet (0 . 0, L (xalr) /BD (blr) ) ;
a decompose (&vT, ~vL, E~ vDs(&vfamax, FAMAX(blr)),
a vDs (&~r~~bmax, FBMAX (blr) ) , &fa [l] , &fb [l] ) ;

CA 02415685 2003-02-04
nor (ssq = 0.0, i - iG; i < E REPS-1; i++) { /* sum up the squared errors*/
ssq += e_norm2( /* on steady-state boiler */
e_vSv(&vT, /* concentrations due to */
ewPv(&vL, /* the various perturbations*/
e_vec ( ( fa [i] *P04A (blr) +fb [i] *P04B (blr) ) /BD (blr) ,
(fa [i] *NAA(blr) +fb [i] *NAB (blr) ) /BD (blr)
);
if ( sqrt(ssq) > MAX RELATIVE ERROR(blr) * a norm(&vT) ) {
blr->updatestatus ~= E INDE~'ERMINATE; /* variability of steady-state */
blr->updatestatus - E~NOTUPDATED; /* boiler concentrations is too big*/
if ((E INCONSISTENT P04 .~ E INCONSISTENT PH) & blr->updatestatus)
blr->updatestatus ~= E;~NOTUPDATED; /* feed program not updated */
/* if P04 or pH are inconsistent*/
/* with blowdown or Na leak ranges*/
if (E NOTUPDATED & blr->updatestatus) goto nochange;
/* definethe steady
state feasable
region in
boiler conc
space....
*/


vV [ 0 - *e vPv ( a vF>v ( &vfamin,&vfbmin) ) ;
] &vL,


vV[1] - *e-vPv(&vL, e-vPv(&vfamax,&vfbmin));
-


vV[2] vPv(&vL, vPv(&vfamax, &vfbmax));
- *e- e


vV[3] _ _ &vfbmax));
- *e vPv(&vL, vPv(&vfamin,
e


vV [4 - vVTO ] ; _
]


/* ...
and the
current
point
*/


vP - *e vec(P04(blr),a na(P04(blr) ,PH(blr), BDTEMP(blr), NH:3(blr)));


/* compute the steady-state feed rate, defined to be the point within the
feasable region that is closest to the target wedge, and, secondarily, if
the feasable region and target wedge overlap, closest to the target point.*/
if ((NVW=a intersect regions(vVW, vW, NW, vV, NV)) > 0) {
/*regions intersect */
if (e within-region(&vT, vVW, NVW))
vSS = vT;
else /* target not in feasable region*/
vX~O] - vX[1] - vT;
vSS = *e region2re ion(vVW, NVW, vX, 1);
blr->updatestatus ~= E_POINT UNMAINTAINABLE;
else /* regions are disjoint*/
blr{>updatestatus E POINT UNMAINTAINABLE;
blr->updatestatus I= E BOX_UNMAINTAINABLE;
vSS = *e region2regionwV, NV, vW, NW);
blr->dt[0] - blr->dt[1] - 0.0; /'* set default feed program to */
vF [O] - vF [1] - vF [2] - vSS; / * steady state feed rates */

CA 02415685 2003-02-04
/* Adjust na/po4 ratio (stage :L of feed program) */
(E FALSE==a within-region(&vP, vW, NW)) ( /* if already in the box */
lnt i = 0, j-= 0; /* no stage 1 adjustment required */
E LOGICAL region unreachable = E_TRUE;
for (i=0; i < NV; i++) {
if ( ! a vEv (&vP, &vV [i] ) &&
e_perimetercrossin.gs(vX, vW, NW, &vP, &vV[i]) > 1) {
for (j=0; j < ~% ~++) { &vV[i] ) )
if (e between(~vP, &vX[j], {
if Tregion unreachable)
vminV = vV [i] ;
vminw = vX[j];
region_unreachable = E_FALSE;
}lse i.f (e norm(e vSv(&vV[i],&vX[j]))*e norm(e vSv(&vminV,&vP)) >
a norm(e~_ vSv(&vminV, &vminW) ) *e-_norm(e-_vSv(&:vV[i] , ~vP) ) ) {
vminV = vV [i] ; -
vminW = vX [ j ] ;
for (i=0; i < NW; i++) {
if (!e vEv(&vP, &vW[i] ) &&
a erimetercrossings(vX, vV, NV, &vP, &vW[i]) > 1) {
for ~=0; j < 2; j++) {
if (e between(&vP, &vW[i] , ~vX[j] ) ) {
if region unreachable) {
vminV = vX[j];
vminW = vW [ i ] ;
region unreachable = E FALSE;
_ _
else if (e norm (e vSv(&vX[j] ,~vW[i] ) ) *e norm (e vSv(&vminV, &vP) ) >
e~norm(.ewSv(&vminV,&vminW)) *e norm(e vSv(&vX[j],&vP))){
vminV = vX [ j ] ;
vminW = vW [ i ] ;
}f (region unreachable==E TRUE ~~
( e_norm ( a vSv ( &vminV , ~&vminW ) ) < _ .
2 0*DBL I~IN*e norm(e vSv(&vminV, &vP))))
blr{>updatestatus ~= E_BOX UNREACHABLE;
else
blr->dt [0] - - (M (blr) /BD (blr) )
loge norm(e vSv(&vm~.nV,&vminW))/e_norm(e vSv(&vminV, &vP)));
vF [ 0 ] - vminV;
vP = vminW;
if (DT(blr,0) > MAX_OUTOFBOX ADJUSTMENT(blr)) {
blr->updatestatus ~= E OUTOFBOX TOOLONG;
_ _

CA 02415685 2003-02-04
/* :Stage 2: move to the setpo:int po4 and NA/po4 ratio if you can */
if vEv(&vP, &vT))
i~_ ve~ erimetercrassin s(vX, vV, NV, &vP, &vT) <= 1)
b{r->updatestatus ~= E_POINT UNREACHABLE;
a:Lse
int i = 0;
E LOGICAL point unreac{able = E_TRUE;
for (i=0; i < 2; i++)
if (e between (&vP, &vT, &vX [i] ) ) {
if Tpoint unreachable; {
vminV =-vX [i] ;
point unreachable = E_FALSE;
}lse if (e norm(e vSv(&vX[i] , &vT) ) *e norm (e vSv(&vminV, &vP) ) >
e-norm(e-vSv(&vminV,&vT))*e-norm(e-vSv(&vX[i],&vP)))
vminV = vX [ i ] ;
}f (E TRUE==point unreachable
a norm(e vSv(&vminV,&vT)) <= 2.0*DBL MIN*e norm(e vSv(&vminV, &vP)))
bTx->updatestatus ~= E_POINT_UNREACH~BLE;
else
blr{ >dt [1] - - (M (blr) jBD (blr) )
log(e_norm(e,vSv(&vmi:nV,&vT))/e~norm(e vSv(&vminV, &vP)));
vF[1] - vminV;
for (i=0; i < 3; i++) { /* determine feed rates needed to attain feed points
*/
a decompose(&vF[i], &vL, a vDs(&vfamax,blr->famax),
-- a vDs (&vfbmax,Flr->fbmax) , &blr->fa [i] , &blr->fb [i] ) ;
b:Lr->fa [i] - a forceinrange (blr->fa [i] , b1r->famin, blr->famax) ; /*fixes
up*/
b:Lr->fb[i] - e-forceinrange(blr->fb[i],blr->fbmin,blr->fbmax);/*roundoff*/
if (T(blr) - LASTT(blr) > MAX SAMPLE INTERVAL(blr))
b:Lr->updatestatus ~= E_SAMPLEINTERVAL,TOOLONG;
blr->lastt - blr->t; /* make prey current values last. values */
blr->lastpo4 - blr->po4;
blr->lastnh3 - blr->nh3;
blr->lastph - blr->ph;
blr->lastbdtemp - blr->bdtemp;
noclzange: /* if we jump to here, feed program not updated*/
return(blr->updatestatus);

CA 02415685 2003-02-04
con:.=t
E
BOILER
*e
undefined
boiler(void)


static
const
E
BOILER
E_UNDEFINED
BOILER
=


NAN,E NAN,E NAN,E NAN,E NAN,
NAN,E NAN,E
NAN,E
NAN,E
NAN,E_NAN,E
NF~""''E'
E
~


_ NAN,E NAN,E NAN,E NAN,E
_ NAN,E NAN,
_
_NAN,E
NAN,E
N~,
NAN,E
NAN,E~~NAN,E
NAN,E
E


_ NAN,E NAN,E NAN,E NAN,E
_ NAN.E NAN,
NAN,E
NAN,E
NAN,E
NAN,E
NAN,E~NAN,E
NAN,E
E


_ _ _
_ NAN,E ~NAN,E NAN,E NAN,E
_ NAN,E NAN,
E
N.AN,E
NAN,E
NAN,E
NAN,E
NAN,E
NAN,E
NAN,E


x.0,0.0} ,{E E NAN , - _
NAN,E NAN,~
L~1AN,E NAN,E
I~AN~, -


0.0,0.0} ~ NAN ~ ,
(E E NAN
NAN,E E -
NAN,E NAN.E
NAN
,


o.o,o.o, o.o,o'.o, o.a,o.o~, o.o;o.o, o.o,o.o ,
~


o.o,o.o o.o,o.o , o.o,o.o;, o.o,o.a, o.o,o.o ,
, ~



o.o,o.o 0.0,0.0 , 0.0,0.0~ 0.0,'0.0, 0.0,0.0 ,
, ~


0.0,0.0 O.O,o.O ~ ,
~ 1~


~0.0,0.0
. ~ ~ . ~ , ,
, , ~
. ~


FALSE};
E
UPDATEOK,
E
UPDATEOK,
E
INITOR,
E


-
return(&E
UNDEFINED
BOILED);


/* a hpm2po4: converts hmp (expressed as % by weight) to ortho P04 */
/* (also in % by weight) */
double a hmp2po4(double hmp)
returnZ0.93135 * hmp); /* conversion factor provided by SMB Aug 4 */

CA 02415685 2003-02-04
/* macc4ssp.h */
#def IDC PASSWORD 155
~


#defineIDC 156
LOCKWINDOW


#defineIDC 157
DATETIME


#defineMACC4SSP 2
ICON


#defineMACC4SSP 1
DIALOG


#defineIDC GROUPBOX1 101


#defineIDC _
_BD 152


#defineIDC BDMIN 131


#defineIDC BDMAX 132


#defineIDC _
M 130


#defineIDC _ 127
BDTEMP


#defineIDC DPHSETPOINT126


#defineIDC _ 125
PHSETPOINT


#defineIDC DPH 124


#defineIDC PH 123


#defineIDC MAXP04 120


#de:fineIDC MINP04 119


#de:fineIDC P04SETPOINT118


#de:fineIDC DP04 117


#de:fineIDC P04 116


#de:fineIDC SPGA 146


#de:fineIDC SPGB 114


#de:fineIDC P04B 112


#de:fineIDC FBDEF 111


#de:fineIDC FBMAX 110


#de:fineIDC FBMIN 109


#de:fineIDC RATION 107


#de:EineIDC RATIOA 106


#de:fineIDC P04A 105


#de:fineIDC FADEF 104


#de:fineIDC STATUSFLAGS151


#de:fineIDC POINTNAMES 154


#de:fineIDC START 121


#de:fineIDC _ 147
SIMULATESSP


#de:EineIDC NH3ENABLED 162


#de:fineIDC NH3TEXT 163


#de:fineIDC NH3 164


#defineIDC AFEED 200


#defineIDC BFEED 201


#defineIDC FAMAX 103


#defineIDC FAMIN 102


#defineIDC GROUPBOXS 129


#defineIDC MESSAGELINE145


#defineIDC LOGTOFILE 113


#defineIDC GROUPBOX6 148


#defineIDC ENGINES 144


#defineIDC ENGINEB 143


#defineIDC 142
ENGINE?


#defineIDC ENGINE6 141


#defineIDC ENGINES 140


#defineIDC ENGINE4 139


#defineIDC ENGINE3 138


#define_ ENGINE2 137
IDC


#defineIDC ENGINE1 136


#defineIDC ENGINEO 135


#defineIDC STOP 122


#defineIDC UNDO 204


#defineIDC REINIT 203


#defineIDC UPDATE 202


#defineIDC LOCALINPUT 158


#defineIDC FORECASTTIME
149




CA 02415685 2003-02-04
#defineIDC FORECAST 150


#deLineIDC GROUPBOX4 -1


#defineIDC GROUPBOX3 2.15


#def'~.IDC GROUPBOX2 108


#def_._aIDC FWFLOW 128


#defineIDC FWNA 161


#defineIDC MAXFWNA 160


#def:ineIDC MINFWNA 159


#def:ine IDC AFEED 200 /* SSP communication id's */
#def:ine IDC BFEED 201 /*lIDC_PH, IDC_P04, and IDC,BDTEMP also used)*/
#def:ine IDC FLAG1 205
#def:ine IDCiFLAG2 206
#def:ine IDCiFLAG3 207
#def:ine IDC FLAG4 208
#def:ine IDC FLAGS 209
#de f: ine IDC_FLAG6 210
#def:ine IDC FLAG? 211
#def:ine IDC FLAGB 212
#def:ine IDC_FLAG9 213
#def:ine IDC FLAG10 214
#def:ine IDC FLAG11 215
#def:ine IDC FLAG12 216
#def:ine IDC RATIO 217
#def:ine IDC T 300 /* E_BOILER field ids that do not have */
#def:ine IDC NAP04RATI0 301 /* a control associated with them */
#def:ine IDC MINNAP04RATI0 302
#define IDClMAXNAP04RATI0 30:3
#def:ine IDC MAX SAMPLE INTERVAL 304
#def=ine IDC MAX OUTOFBOX ADJUSTMENT 305
#def:ine IDC MAX RELATIVE~ERROR 306
#define IDC BEPS 307
#define IDC FDT 308
#define IDC LASTT 309
#define IDC LASTBDTEMP 310
#define IDC LASTP04 311
#dei=ine IDC LASTPH 312
#dei=ine IDC B4LASTT 313
#dei=ine IDC B4LASTBDTEMP 314
#de:°ine IDC B4LASTP04 315
#define IDC~B4LASTPH 316
#de:Eine IDC LASTED 319
#de:Eine IDC LASTL 320
#de:Eine IDC DT1 321
#de:fine IDC DT2 322
#de:fine IDC FA1 323
#define IDC FA2 324
#define IDC~FA3 325
#define IDC FB1 326
#define IDC FB2 327
#define IDC~FB3 328
#define IDC LASTDT1 329
#define IDC LASTDT2 330
#define IDC LASTFA1 331
#define IDC LASTFA2 332
#define IDC LASTFA3 333
#define IDC LASTFB1 334
#define IDC LASTFB2 335
#define IDC LASTFB3 336
#define IDC~UPDATESTATUS 337
#define IDC LASTUPDATESTATUS 338
#define IDC_INITSTATUS 339
#define IDC UNDOABLE 340
#de:fine IDC_LASTNH3 341
#de:fine IDC B4LASTNH3 342

CA 02415685 2003-02-04
/* misc/MACC4SSP specific/state/control/error ids */
#de~ °_ IDC CURRENTENGINEID ~:~01
#dei~ae IDC~RUNNING 402
#define IDC FLAGSUPTODATE 403
#define IDC ACTIONSUPTODATE 404
#define IDC~SCIERR 405
#define IDC PIDSCIERR 406
#de:fine IDC~ACTION 407
#define IDC~CHECKSUM 408
#define IDC LOADFORM 409
#define IDC LASTEVENTTIME 410
#define IDC~EVENTID 411
#define IDC STARTTIME 412
#de~fine IDC LAUNCH 413
#define IDC~CLOSE 414

CA 02415685 2003-02-04
*~l~**************************************************************************
mace ~sp.rc
produced by Borland Resource Workshop
*****************************************************************************~
#de~fine IDC PASSWORD 155
#defineFWNA 161
IDC


#define_ 160
IDC MAXFWNA


#defineIDC MINFWNA 159


#defineIDC LOCKWINDOW 156


#defineIDC DATETIME 157


#defineMACC4SSP ICON 2


#include<windows.h>


#defineMACC4SSP DIALOG1


#defineGROUPBOX1 101
IDC


#define_
IDC BD 152


#defineIDC BDMIN 131


#def II7C BDMAX 13 2
ine


#defineM 130
IDC~


#define_ 127
IDC BDTEMP


#de:fineDPHSETPOINT 126
IDC


#define_ 125
PHSETPOINT
IDC


#define_
IDC DPH 124


#defineIDC PH 123


#defineIDC MAXP04 120


#defineIDC MINP04 119


#defineIDC~P04SETPOINT118


#defineIDC DPO4 117


#defineIDC P04 116


#defineIDC NH3TEXT 163


#defineIDC NH3 164


#defineIDC SPGA 146


#defineIDC SPGB I14


#defineIDC P04B 112


#defineIDC FBDEF 111


#defineIDC FBMAX 110


#define~IDC FBMIN 109


#defineIDC RATIOB 107


#defineIDC RATIOA 106


#defineIDC P04A 105


#defineIDC FADEF 104


#defineIDC STATUSFLAGS151


#defineIDC POINTNAMES 154


#defineIDC NH3UNDERLINE
165


#defineIDC START 121


#defineIDC SIMULATESSP147


#defineIDC AFEED 200


#defineIDC BFEED 201


#defineIDC FAMAX 103


#defineIDC FAMIN 102


#defineIDC GROUPBOX5 129


#defineIDC MESSAGELINE145


#defineIDC'LOGTOFILE 113


#defineIDC NH3ENABLED 162


#defineIDC FWFLOW 128


#defineIDC~GROUPBOX6 148


#defineIDC ENGINES 144


#defineIDC ENGINES 143


#defineIDC ENGINE? 142


#defineIDCJENGINE6 141



CA 02415685 2003-02-04
#define IDC ENGINE5 140
#define IDC ENGINE4 139
#defe IDC ENGINE3 138
#def ' IDC ENGINE2 137
#def~..e IDC ENGINE1 136
#define IDC ENGINEO 135
#define IDC_STOP 122
#define IDC UNDO 204
#define IDC REINIT 203
#define IDC_UPDATE 202
#define IDC LOCALINPUT 158
#define IDC FORECASTTIME 149
#define IDC FORECAST 150
#define IDC GROUPBOX4 -1
#define IDC GROUPBOX3 115
#define IDC GROUPBOX2 108
MACC4SSP_DIALOG DIALOG 1, l, 352, 248
STYLE WS OVERLAPPED ~ WS_CAPTION ~ WS~SYSMENU ~ WS MINIMIZEBOX
CLASS "MACC4SSP" -
CAPTION "MACC4SSP Boiler0 (ERROR NONE)"
FONT 8, "MS Sans Serif"
LTEXT "Password:", -1, 4, 4, :34, 8
EDITTEXT IDC PASSWORD, 41, 2, 43, 12, :~S UPPERCASE ~ ES_PASSWORD ( WS BORDER
PUSHBUTTON "Lock", IDC LOCKWINDOW, 87, 2, 20, 12
CO:~TTROL "Last update:", -1, "STATIC", SS LEFTNOWORDWRAP ~ WS CHILD ( WS
VISIBLE
CO:~ITROL "24-Dec-1994 09:24:29", IDC DATETIME, "STATIC", SS LEFTNOWORDWRAP ~
WS
GROUPBOX "Pump/Tank A", IDC GROUPBOX1, 4, 14, 109, 91, BS GROUPBOX
LT:EXT "Min Feed (gpd) :", -1, 12, 30, 52, 12
EDITTEXT IDC FAMIN, 70, 28, 4(), 12
C01VTROL "Max Feed (gpd):", -1, "STATIC", SS LEFTNOWORDWRAP ~ WS,CHILD ~ WS
VISI
EDITTEXT IDC FAMAX, 70, 40, 4(), 12
CO1VTROL "Init. Feed (gpd):", -1, "STATIC", SS_LEFTNOWORDWRAP ~ WS CHILD ~ WS
VI
EDITTEXT IDC FADEF, 70, 52, 40, 12 - '-
LT;~XT "P04 (% by wt.) :", -1, 12, 66, 53, 12
EDITTEXT IDC P04A, 70, 64, 40, 12
LTEXT "Na/P04 ratio:", -1, i2, 78, 52, 12
EDITTEXT IDC RATIOA, 70, 76, 40, 12
LT:EXT "Specific Gravity:", -1, 12, 90, 55, 12
EDITTEXT IDC SPCA, 70, 88, 40,, 12
GROUPBOX "Pump/Tank B", IDC GROUPBOX2, 118, 15, 109, 90, BS GROUPBOX
C01VTROL "Min Feed (gpd):", -1, "STATIC", SS,LEFTNOWORDWRAP T WS~CHILD ~
WS_VISI
EDITTEXT IDC FBMIN, 183, 28, 40, 12
CO1VTROL "Max Feed (gpd):", -1,, "STATIC", SS~LEFTNOWORDWRAP ~ WS__CHILD ~ WS
VISI
EDITTEXT IDC FBMAX, 183, 40, 40, 12
CO1VTROL "Init. Feed (gpd):", -1, "STATIC", SS'LEFTNOWORDWRAP ~ WS CHILD ~
WS_VI
EDITTEXT IDC FBDEF, 183, 52, 40, 12
LT:EXT "P04 (% by wt.):", -1, 127, 64, 52, 12
EDITTEXT IDC P04B, 183, 64, 4c), 12
LT:EXT "Na/P04 ratio:", -1, 12'7, 76, 49, 12
EDITTEXT IDC RATIOB, 183, 76, 40, 12
LTEXT "Specific Gravity:", -1, 127, 88, 56, 12
EDITTEXT IDC SPGB, 183, 88, 40, i2
GROUPBOX "Boiler Phosphate", IDC GROUPBOX3, 4, 109, 109, 91, BS GROUPBOX
C01VTROL "Phosphate (ppm):", -1, "STATIC", SS_LEFTNOWORDWRAP ~ WS CHILD ~ WS
VI:
EDITTEXT IDC P04, 70, 122, 40, 12
LTEXT "Variability (ppm):", -1, 11, 136, 55, 10
EDITTEXT IDC DP04, 70, 134, 40, 1.2
LTEXT "Set Point (ppm):", -1., 10, 147, 55, 9
EDITTEXT IDC P04SETPOINT, 7C!, 146, 40, 12
LTEXT "Min (ppm):", -1, 10, 1.59, 54, 10
EDITTEXT IDC MINP04, 70, 158, 40, 12
LTEXT "Max (ppm):", -1, 10, 171, 52, 10
EDITTEXT IDC MAXP04, 70, 170, 40, 12
CONTROL "pH:", -1, "STATIC", SS LEFTNOWORDWRAP ~ WSVCHILD ~ WS VISIBLE ~ WS
GRc
EDITTEXT IDC PH, 184, 123, 9:0, 12

CA 02415685 2003-02-04
LTEXT "Variability:", -1, 128, 137, 38, 12
EI>.ITTEXT IDC DPH, 184, 13S, 40, 12
L:.'EXT "Setpoint:", -1, 128, 150, 30, 10
EI)T~~'EXT IDC PHSETPOINT, 15"7, 149, 31, 13
LTl "+/_~~~-_1, 188. 151, 11, 9
EDITTEXT IDC DPHSETPOINT, 199, 149, 25, 13
EDITTEXT IDC_-BDTEMP, 184, 1E~4, 40, 12
CHECKBOX " ", IDC NH3ENABLED, 119, 176, 9, 11, BS AUTOCHECKBOX ~ WS TABSTOP
CONTROL "NH3 (ppm)*:", IDC NH3TEXT, "STATIC", SS LEFTNOWORDWRAP ~ WS-CHILD ~
WS
EDITTEXT IDC NH3, 184, 176, 40, 12
CONTROL "", IDC NH3UNDERLINF, "static", SS BLACKFRAME ~ WS CHILD ~ WS VISIBLE
LTEXT "* of boiler water pH sample", -1, 1$0, 190, 106, 9
GROUPBOX "Other Parameters" IDC GROUPBOX5, 231, 109, 117, 91, BS GROUPBOX
CONTROL "BW Mass (lbs):", =, "STATIC", SS LEFTNOWORDWRAP ~ WS-CHILD ~ WS VISIB
EDITTEXT IDC M, 297, 122, 4~), 12
CONTROL "FW Flow (lbs/hr):" -1, "STATIC", SS LEFTNOWORDWRAP ~ WS-CHILD ~ WS VI
EDITTEXT IDC FWFLOW, 29?, 134, 49, 12
CONTROL "Min:", -1, "STATIC", SS_LEFTNOWORDWRAP WS_CHILD WS VISIBLE WS_GR
CONTROL "Max:", -1, "STATIC", SS LEFTNOWORDWRAP WS_CHILD WS VISIBLE WS_GR
CONTROL "Est.", -1, "STATIC", SS-LEFTNOWORDWR.AP WS CHILD WS-VISIBLE WS GR
CONTROL " BD (lb/hr)", -1, "STATIC", SS LEFTNOWORDWRAP ( WS CHILD ' WS VISIBLE
EDITTEXT IDC BDMIN, 248, 159, 49, 12
EDITTEXT IDC HDMAX, 248, 171., 49, 12
EDITTEXT IDC BD, 248, 183, ~9, 12
LTEXT "FW Na (ppb)", -1, 29~'', 150, 49, 10, WS BORDER ~ WS GROUP
EDITTEXT IDC MINFWNA, 297, i.59, 49, 12 - -
EDITTEXT IDC MAXFWNA, 297, 1.71. 49, 12
EDITTEXT IDC FWNA, 297, 183, 49, 12
CONTROL "Model Adaptive Congruent Controller 4 Smartscan+ 1Ø Copyright (c)
Be
PUSHBUTTON "Boiler0", IDC ENGINEO, 1, 217, 35, 14, WS GROUP ~ WS TABSTOP
PUSHBUTTON "Boiled", IDC ENGINEl, 36, 217, 35, 14
PUSHBUTTON "Boiler2", IDC ENG:LNE2, 71, 217, 35, 14
PUSHBUTTON "Boiler:3", IDC ENG:LNE3, 106, 217, 35, 14
PUSHBUTTON "Boiler4", IDC' ENGINE4, 141, 217, 35, 14
PUSHBUTTON "Boilers", IDC' ENGINES, 176, 217, 35, 14
PUSHBUTTON "Boiler6", IDC'~ENGINE6, 211, 21?, 35, 14
PUSHBUTTON "Boiler?", IDC ENGTNE7, 246, 217, 35, 14
PUSHBUTTON "Boiler8", IDC-ENGTNEB, 281, 217, 35, 14
PUSHBUTTON "Boiler9", IDC EN'GINE9, 316, 217, 35, 14
COLVTROL "Point names:
m0a~eed,mObfeed,mOpH,mOP04,mObdtemp,m0update,m0undo,mOrei
PUSHBUTTON "Stop", IDC STOP, 231, 4, 58, 14
PUSHBUTTON "Start", IDC START, 289, 4, 59, 14
CH'ECKBOX "Simulate SSP", IDC_SIMULATESSP, 231, 20, 54, 14, BS AUTOCHECKBOX ~
WS
CHECKBOX "Local Inputs", IDC LOCALINPUT, 295, 21, 53, 12, BS AUTOCHECKBOX ~
WS_
PUSHBUTTON "Update", IDC UPDATE, 231, 35, 39, 12
PUSHBUTTON "Undo", IDC UFTDO, 270, 35, 39, 12
PUSHBUTTON "Reinit", IBC REINIT, 309, 35, 39, 12
GROUPBOX "Outputs to SmartScan+", IDC GROUPBOX6, 231, 48, 117, 44, BS GROUPBOX
LTEXT "Feed Rate A (gpd):", -l, 235, 59, 64, 12
LTEXT "", IDC AFEED, 298, 59, 45, 10, WS BORDER ~ WS-GROUP
LTEXT "Feed Rate B (gpd):", -1, 235, 68,-64, 12
LTEXT "", IDC BFEED, 298, 68, 45, 10, WS BORDER ~ WS GROUP
LTEXT "Status-Flags:", -1, 235, 79, 43,
LTEXT "", IDC STATUSFLAGS, 292, 78, 51, 10, WS BORDER ( WS GROUF
EDLTTEXT IDC FORECASTTIME, 231, 94, 17, 1.2
PUSHBUTTON "F~r P04,ipH forecast", IDC FORECAST, 248, 94, 66, 12
CHECKBOX "Logfile" IDC LOGTOFILE, 316, 95, 32, 12, BS AUTOCHECKBOX ~ WS TABSTC
CO1VTROL "", -1, "static", SS BLACKFRAME WS CHILD WS VISIBLE, 128, 133, 11,
C01VTROL "", -1, "static", SS-i3LACKFR.AME WS CHILD WS'VISIBLE, 233, 192, 12,
CONTROL "", -1, "static", SS'-BLACKFRAME WS-CHILD WS VISIBLE ~ WS_BORDER, 11
CONTROL "", -1, "static", SS~BLACKFRAME WS CHILD WS VISIBLE WS BORDER, 12
CO1VTROL "Temperature (F)*~", -1, "STATIC'°, S~ LEFTNOWOR~WRAP ~ WS
CHILD ~ WS VI
GROUPBOX -"Boiler pH", IDC-GROUPBOX4, i18, 109, 109, 91, BS GROUFB~X

Image

CA 02415685 2003-02-04
/* macc4ssp.c: Model Adaptive Congruent Controller Engine for SmartScan Plus*/
/* vgrsion 1Ø Copyright (c) Betz Labs, 1994. All rights reserved. */
/* w~,.tten by John Gunther, R&D Computer Applications Groug, July, 1994 */
/* (extension 2760, Trevose office) */
#include <windows.h>
#in.clude <string.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <dos.h>
#include "scintdll.h" /* SmartCard Interface Library */
#include "macc4.h" /* Model Adaptive Congruent Controller, version 4*/
#include "macc4ssp.h" /* Identifiers for fiel3s, other parameters */
#define TESTING 0 /* determines the test pattern used in operator sim.*/
#define WORKAROUND 0 /* used to workaround memory leak in SOIL version lAl*/
#if TESTING
#define SCILERRORFRACTION 0.05 /* used during dey. to produce SCIL errors */
#else
#define SCILERRORFRACTION 0.0 /* no SCIL errors in production simulations*/
#endif
#define ROOMTEMPERATUREDEGREESF 77.0
#define UNDOINTERVAL 1.0 /* only give them 1 hour to undo */
#define M4SMODULENAME "MACC4SSP"
static HWND hmainwin = NULL; /* handle to main (dialog) window */
#define DOUBLEPRECISIONFORMAT "%.lOg" /* full precision doubles */
#define FLOATPRECISIONFORMAT "%.6g" /~* full precision floats */
#define SHORTFLOATPRECISIONFORMAT "%.5g" /* helps squeeze stuff in sometimes
*/
#define MAXSTRING 100
#define IGNORECHECKSUM -1 /* if checksum is set to this, it is ignored*/
#define SIMHOURSPERREALSECOND 1.0
#define SIMHOURSPEREVENT 24.0
#define SECONDSPERHOUR 3600
#define M4STIMERID 1
#define DEFTIMERINTERVAL 3000 /* in milliseconds */
#define NANINT -32767 /* undefined integer */
#define NANFLT E NAN /'* undefined floating point value (from macc4.h)*/
#define NANSTRIN~ "" /* string corresponding to undefined value */
#define NANBOOL -1 /* used with BOOLPLUS type to allow "undefined"*/
typedef enum boolplus { /* extends BOOL to include NANBOOL ("not a number") */
HP TRUE=TRUE. BP_FALSE=FALSE, BP NANBOOL=NANBOOL,
BOOLPLUS;
typedef struct simsspevent { BOOLPLUS update, undo, reinit; ~ SIMSSPEVENT;
SIMSSPEVENT simsspinputs(1 -
{
#if TESTING
TRUE, FALSE, FALSE},
FALSE, TRUE, FALSE},
FALSE, FALSE, TRUE},
TRUE, TRUE, TRUE}
FALSE, TRUE, TRUE ,
TRUE, FALSE, TRUE ,
TRUE, TRUE, FALSE ,
#endif

CA 02415685 2003-02-04
{TRUE, FALSE, FALSE},
};
stav~4~ SIMSSPEVENT nullevent - {FALSE, FALSE, FALSE};
#define MINENGINEID 0
#define DEFENGINEID 0
#define MAXENGINEID 9
typ~~def int M4SENGINEID;/* all reference to M4SENGINE objects is via this id
*/
typedef int M4SPARAMID; /* identifies a parameter used by the M4SENGINE */
/* (c.f. m4sparameters and/or macc4ssp.h for list of defined pids) */
typ~~def struct m4sengine { /*in memory representation of a MACC4SSP Engine */
BOOL running; /*is the engine running? */
SCIError scierr; /*Ist SmartCard Interface Library error code seen */
M4SPARAMID pidscierr; /*identifier of parameter SCIL error involves*/
BOOL actionsuptodate; /*SSP action codes MACC4SSP maintains up-to-date?*/
BOOL flagsuptodate; /*SSP flags MACC4SSP maintains up-to-date?*/
int checksum; /* used to validate engine state I/O */
BOOL badchecksum; /* signals a corrupted INI file (invalid state)*/
BOOL logtofile; /* .~.og actions to *.LOG file?*/
BOOL simulatessp; /* simulate SSF inputs? (for testing)*/
BOOL localinput; /* Gaccept inputs 1oeo1ly?*/
BOOL nh3enabled; /* use an ammonia correction for pH's?*/
double starttime; /* (actual, real world) time at which run began */
double lasteventtime; /* time of last simulated operator input*/
int eventid; /* cursor into table of simulated operator inputs*/
SIMSSPEVENT currentevent; !* simulated or locally generated events*/
double fwflow; /* feedwater flow--used to scale no leak */
E BOILER blr; /* MACC4 controller as defined in macc4.h */
}_
M4 SENG I NE ;
M4SENGINE m4e[MAXENGINEID+1];
static int currentengineid = DEFENGINEID;
#de:fine xquote(x) #x
#de:fine quote(x) xquote(x) /* forces expansion of x before quoting it */
# define M SAY(s) MessageBox(hmainwin,s,"Informational message",MB OK)
# d.=_fine M BUGALERT(s) MessageBox(hmainwin,s,\
"MACC4SSP Eug in file: " quote(~FILE_) " at line: " quote( LINE_),MB_OK)
#de:fine M ASSERT(cond) \
if (!(con3)) MessageBox(hmainwin, "Failed assertion: " #cond,\
"As;sertion Failure in File: " quote( FILE ) " at Line: " quote( LINE ),\
MB ICONHAND~MB OK)
#define M M4SINF0(s) clearmarque(),marque(s)
#define M M4SERROR(s) MessageBeep(0),clearmarque(),marque(s)
#define M M4SERROR2(s) MessageBeep(0),MessageBeep(0),clearmarque(),marque(s)
#de f ine lei M4 S FATALERROR ( s ) \
Messa eBox(hmainwin, s, "MACC4SSP fatal error--cannot continue.",\
MB-~K~MB_ICONEXCLAMATION)
/*
Initialization error message information. Index of array is integer
code as defined in the initstatus enum in macc4.h.
*/
struct initerror {char *msg; /* message to display for initstatus error*/
M4SPARAMID field;} /*id of error related control to move to*/
initerr [] _ {
"E INITOK: Initialization OK", IDC STOP},
"E MISSING T: required field t (time) has not been specified."', IDC STOP}
"E MISSING FAMIN: required field famin has not been specified", IDC FAMIN ,
"E MISSING FAMAX: required field famax has not been specified", IDC FAMAX ,
"E MISSING FADEF: required field fadef has not been specified", IDC FADEF ,
"E MISSING P04A: required field po4a has not been specified", IDC_P04A},
"E_MISSING RATIOA: required field ratioa has not been specified",

CA 02415685 2003-02-04
IDC RATIOA},
~"E MISSING FBMIN: required field fbmin has not been specified",
TDC FBMIN~,
'MISSING FBMAX: required field fbmax has not been specified", IDC FBMAX ,
_MISSING FBDEF: required field fbdef has not been specified", IDC FBDEF~,
"E MISSING P04B: required field po4b has not been specified", IDC P04B},
"E MISSING RATIOB: required field ratiob has not been specified",-
IDC RATIOB},
"E MISSING P04: required field po4 has not been specified", IDC P04},
"E MISSING PH: required field ph has not been specified", IDC_PH},
"E MISSING M: required field m (mass) has not been specified", IDC M),
"E MISSING BDTEMP:. required field bdtemp has not been specified", -
IDC BDTEMP},
{"E MISSING P04SETPOI:I~T: required field po4setpoint has not been specified",
IDC_P04SETPOINT1,
{"E MISSING MINP04: required field minpo4 has not been specified",
IDC MINP04~,
{"E MISSING MAXP04: required field maxpo4 has not been specified",
IDC MAXP04~,
{"E MISSING RATIOSETPOINT: neither phsetpoint nor napo4ratio were specified"
IDC PHSETPOINT},
{"E; BDMAX LT BDMIN: max blowdown rate, bdmax < min blowdown rate,, bdmin",
IDC BDMIN},
{"E; NALEAKMAX LT NALEAKMIN: max Na leak, naleakmax < min Na leak, naleakmin",
IDC MINFWNA},
{"E; FAMAX LE FAMIN: max pump a flow, famax <= min pump a flow, famin",
IDC FAMIN},
{"E; FBMAX LE FBMIN: max pump b flow, fbmax <= min pump b flow, fbmin",
IDC FBMII3} ,
{"E; MINP04 LT MINMINP04: lower boiler po4 control limit, minpo4 < 1 ppm",
IDC MINP04},
{"E,_MAXP04 GT MAXMAXP04: upper boiler po4 control limit, maxpo4 :> 100 ppm",
IDC MAXP04},
{"E. P04SETPOINT LT MINP04: setpoint boiler po4 < lower control limit",
IDC P04SETP~IN~I},
{"E: P04SETPOINT GT MAXP04: setpoint boiler po4 > upper control limit",
IDC P04SETPOINT~,
{"E,_P04A LT ZERO: pump a phosphate concentration < 0",
IDC P04A},
{"E; P04B LT ZERO: pump b phosphate concentration < 0",
IDC P04B},
{"E; MINRATIO LT MINMINRATIO: lower Na/P04 ratio control limit < 2.2",
IDC DPHSETPOINT},
{"E; MAXRATIO GT MAXMAXRATIO: upper Na/P04 ratio control limit > 2.8",
IDC DPHSETPOINT~,
{"E RATIO LT MINRATIO: setpoint Na/P04 ratio < lower control limit",
IDC DPHS~TPOINT~,
{"E; RATIO GT MAXRATIO: setpoin.t Na/P04 ratio > upper control limit",
IDC DPHSETPOINT},
{"E; RATIOA EQ RATIOB: pump a Na/P04 ratio = pump b Na/P04 ratio",
IDC RATIOA},
{"E; MISSING SPGA: required field spga !specific gravity) not specified",
IDC SPGA},
{"E; MISSING SPGB: required field spgb ispecific gravity) not specified",
IDC SPGB}
typedef enum contro:Lsecuritylevel
NULL LEVEL=0, /* no security enforced on the control */
fIANAGERONLY LEVEL=1, /* access only for users who have entered password */
OPERATORONLY LEVEL=2,/* access only for users who have NOT entered password*/
C:ONTROLSECURITYLEVEL;
typedef enum controlaccess {
NULL ACCESS=0, /* unconditional access */
STATIC ACCESS=1, /* control. ONLY accessable when controller is stopped */

CA 02415685 2003-02-04
DYNAMIC ACCESS=2, /* control ONLY accessable when controller is running */
LOCAL ACCESS=4, /*if running, cntrl accessable only if local input checked
- if stopped, cntrl accessable only if user is a manager */
NI, ACCESS=8 /* control ONLY accessible when nh3 is checked */
CONTROLACCESS;
typedef enum controltype {
NULL CONTROL,/*not a control, or a control not stored/restored to/from disk*/
STOREDEDIT CONTROL, /* edit control which is stored/restored to/from disk */
STOREDCHECKBOX_CONTROL /* stored/restored to/from disk checkbox control */
CONTROLTYPE;
typedef struct m4sparam {
int pid;
char *name;
/* remaining fields only apply to parameters that are associated with
one of the on-screen controls */
double low; /* for range checking of edit controls*!
double high;
CONTROLSECURITYLEVEL Level; /* who can access control ? */
CON'TROLACCESS access; /* and under what conditions? */
CONTROLTYPE type; /* stored text (edit ctrl), stored mode (checkbox)*/
M4SPARAM;
#define SMALLNUMBER 1e-15
M4SPARAM m4sparameters[] _
/*misc.ids*/
{IDC CURRENTENGINEID,"current:engineid",NANFLT,NANFLT,NU'LL LEVEL,
NULL ACCESS,NULL CONTROL},
{IDC RUNNING,"running",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
{IDC FLAGSUPTODATE,"flagsuptodate",NANFLT,NANFLT,NU'LL LEVEL,NULLVACCESS,
NULL CONTROL},
{IDC ACTIONSUPTODATE,"actionsuptodate",NANFLT,NANFLT,NULL LEVEL, NULL ACCESS,
NULL CONTROL},
IDC CHECKSUM,"checksum",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
~IDC LASTEVEN'TTIME,"lasteventtime",NANFLT,N~NFLT,NULL~LEVEL,NULL_ACCESS,
NULL CONTROL},
{IDC EVENTID,"eventid",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
/*
dialog box control ids (all parameters which are associated with a
control must be placed between IDC~FIRSTCONTROL and IDC LASTCONTROL)
#define IDC FIRSTCONTROL IDC FORECASTTIME
{IDC FORECASTTIME,"forecasttime",0,999, NULL LEVEL, DYNAMIC ACCESS,
ST'OREDEDIT CONTROL}, /* limit to 999 so it~fits in narrow 3 character field*/
{IDC FORECAST "forecast",NANFLT,NANFLT,NULL LEVEL, DYNAMIC ACCESS,
NULL CONTROL,
{IDC PASSWORD "password",NANFLT,NANFLT,OPERATORONLY LEVEL,NULL'ACCESS,
NULL CONTROL ,
{IDC -LOCKWINDOW,"lockwindow",NANFLT,NANFLT,MANAGERONLY LEVEL, NULL ACCESS,
NLfLL CONTROL } ,
IDC STARTTIME,"starttime",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
~IDC LOGTOFILE,"logtofile",NANFLT,NANFLT,MANA'GERONLY LEVEL, STATIC ACCESS,
STOREDCHECKBOX CONTROL},
{IDC SIMU'LATESS~,"simulatessp",NANFLT,NANFLT,MANAGERONLY LEVEL,STATICrACCESS,
STOREDCHECKBOX CONTROL},
{IDC LOCALINPUT,"localinput",NANFLT,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
S7.'OREDCHECKBOX CONTROL } ,
{IDC MINFWNA,"minfwna",NANFL"r,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL),
{IL)C MAXFWNA,"maxfwna",NANFL'T,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STO~EDEDIT CONTROL},

CA 02415685 2003-02-04
{IDC FWFLOW,"fwflow",SMALLNUMBER,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STO~EDEDIT CONTROL),
{Ii)C BDMIN,"bdmin",SMALLNUMBER,NANFLT,MANAGERONLY LEVEL,STATIC_ACCESS,
ST'~DEDIT CONTROL} ,
{ID~~ 3DMAX,"bdmax",SMALLNUMBER,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC M,"m",SMALLNUMBER,NANFL'T,MANAGERONLY~LEVEL,STATIC~ACCESS,
STOREDEDIT CONTROL),
{II7C DPHSETPOINT,"d~hsetpoint",SMALLNUMBER,1.O,MANAGERONLY LEVEL, STATIC
ACCESS,
STOREDEDIT CONTROL ,
{IDC PHSETP~INT,"phsetpoint",2,13,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC DPH,"dph",SMALLNUMBER,1.O,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
(IDC MAXP04,"maxpo4",1.,1000.,MANAGERONLY~LEVEL,STATIC ACCESS,
S'.COREDEDIT CONTROL} ,
(IDC MINP04,"minpo4",1.,1000.,MANAGERONLY~LEVEL,STATIC ACCESS,
S".."OREDEDIT CONTROL} ,
{IDC P04SETF~OINT,"po4setpoint",1.,1000.,MANAGERONLY LEVEL, STATIC.~ACCESS,
S7.'OREDEDIT CONTROL} ,
{IDC DP04,"3po4",SMALLNUMBER,lO.,MANAGERONLYlLEVEL,STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC SPGA,"spga",SMALLNUMBER,NANFLT,MANAGERONLY~LEVEL,STATIC ACCESS,
STO~EDEDIT CONTROL},
{IDC SPGB,"spgb",SMALLNUMHER,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STO~EDEDIT CONTROL ,
{II)C P04B,"po4b",O,:LOO,MANAGERONLY LEVEL, STATIC ACCESS,
STO~EDEDIT CONTROL},
{II>C FBDEF,=fbdef",O,NANFLT,MANAGERONLYrLEVEL,STATIC ACCESS,
STO~EDEDIT CONTROL),
{IDC FBMAX,"fbmax",O,NANFLT,MANAGERONLYrLEVEL,STATIC ACCESS,
S'1.'O~EDEDIT CONTROL ~ ,
{II>C FBMIN,"fbmin",O,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC RATIOB,"ratiob",O,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC RATIOA,"ratioa",O,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL.},
{IDC P04A,"po4a",O,100,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
(IDC FADEF,"fadef",O,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STO~tEDEDIT CONTROL),
(IDC FAMAX,"famax",O,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL),
{IDC FAMIN,"famin",O,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC MESSAGELINE,"messageline",NANFLT,NANFLT,NULL,LEVEL,NULL ACCESS,
NULL CONTROL},
{IDC NH3ENABLED,"nh3enabled",NANFLT,NANFLT,MRNAGERONLY LEVEL,STATIC_ACCESS,
STO1~EDCHECKBOX CONTROL},
{IDC NH3TEXT,"nfi3text",NANFL'T,NANFLT,NULL LEVEL,NH3 ACCESS,NULL CONTROL},
{IDC START,"START",NANFLT,NANFLT,MANAGERONLY LEVEL,STATIC_ACCES~S,
NULL CONTROL } ,
(ID(: STOP,"STOP",NANFLT,NANFLT,MANAGERGNLY~LEVEL,DYNAMIC,ACCESS,
NULL CONTROL},
/* SSP point names (some also correspond to controls) */
/* (we use uppercase for SmartScan+ point names cause' SmartScan+ */
/* converts point names to uppercase)*/
(IDC BDTEMP,"HDTEMP",32,212,NL1LL_LEVEL,LOCAL~ACCESS,
STUR~DEDIT CONTROL ,
(IDC P04,"P04",SMALLNUMBER,1000.O,NULL_LEVEL,LOCAL ACCESS,
STOR~DEDIT CONTROL},
(IDC NH3,"1~H3",0.0 1000.0,NULL,LEVEL,LOCAL ACCESS~NH3_ACCESS,
STOR~DEDIT CONTROL ,

CA 02415685 2003-02-04
{IDC PH,"PH",1,14, NULL LEVEL, LOCAL ACCESS,
STO~EDEDIT CONTROL}
{IDC ~WNA,"FWNA",NANFLT,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOR- :DIT CONTROL}, -
{ID'S ~D,"BU",SMALLNUMBER,NANFLT,MANAGERONLY LEVEL, STATIC ACCESS,
STOREDEDIT CONTROL},
{IDC UPDATE,"UPDATE",NANFLT,NANFLT,NULL~LEVEL,LOCAL ACCESS~DYNAMIC ACCESS,
NULL CONTROL},
{IDC ~tEINIT,"REINIT",NANFLT,NANFLT,NULL~LEVEL,LOCAL ACCESS~DYNAMIC ACCESS,
NULL CONTROL},
{IDC UNDO,"UNDO",NANFLT,NANFLT, NULL LEVEL,LOCAL~ACCESS~DYNAMIC_ACCESS,
NULL,CONTROL},
#define IDC LASTCONTROL IDC UNDO
IDC AFEED,"AFEED",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC BFEED,"BFEED",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDG RATIO,"RATIO",SMALLNUMBER,NANFLT, NULL LEVEL, NULL ACCESS, NULL CONTROL
IDC FLAG1,"FLAG1",NANFLT,NANFLT,NULL LEVE~,NULL ACCESS,NULL CONTROL ,
IDC FLAG2,"FLAG2",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC FLAGS,"FLAGS",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC: FLAG4,'°FLAG4",NANFLT,NANFLT,NULL LEVEL,NULL~ACCESS,NULL
CONTROL ,
IDC FLAGS,"FLAGS",NANFLT,NANFLT,NULL LEVEL,NULL_ACCESS,NULL CONTROL ,
ID(~ FLAG6,"FLAG6",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC FLAG?,"FLAG?",NANFLT,NANFLT,NULL LEVEL,NULLJACCESS,NULL CONTROL ,
IDC FLAG$,"FLAGB",NANFLT,NANFLT,NULL~LEVEL,NULL ACCESS,NULL CONTROL ,
IDC FLAG9,"FLAG9",NANFLT,NANFLT,NULL~LEVEL,NULL ACCESS,NULL CONTROL ,
IDC: FLAG10,"FLAG10",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC. FLAG11,"FLAG11'",NANFLT,NANFLT,NULL~LEVEL,NULL ACCESS,NULL CONTROh ,
IDC_FLAG12,"FLAG12",NANFLT,NANFLT,NULL_LEVEL,NULL ACCESS,NULL CONTROL ,
/* Must E BOILER fields */
IDC T,"t",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
~IDC: NAP04RATI0,"napo4ratio",~ANFLT,NANF~T,NULL~LEVEL,NULL ACCESS,
NULL CONTROL } ,
{IDC MINNAP04RATI0,"minnapo4ratio",NANFLT,NANFLT,NULLlLEVEL,NULL ACCESS,
NULL CONTROL } ,
{IDC I~AXNAP04RATI0,"maxnapo4ratio",NANFLT,NANFLT,NULL LEVEL, NULL ACCESS,
NULL CONTROL } ,
{IDC: MAX SAMPLE INTERVAL,"max sample interval",NANFLT,NANFLT,
NULL LE~3EL,NUL~ ACCESS,NULL CONTROLS,
{IDC: MAX OUTOFBOX ADJUSTMENT,"max outofbox,adjustment",NANFLT,NANFLT,
NULL LEVEL,NULL ACCESS,NULL CONTROL},
{IDC MAX RELATIVE ERROR,"max~relative error",NANFLT,NANFLT, NULL LEVEL,
NULL ACCESS,NULL CONTROL},
IDC BEPS,"beps",NANFLT,NANFL'r,NULL LEVEL,NULL ACCESS,NULL CONTROL},
IDC' FDT,"fdt",NANFLT,NANFLT,NULL LEVEL,NULL A~CESS,NULL CONTROL},
IDC:~LASTT,"lastt",NANFLT,NANFLT,I~ULL LEVEL,NULL ACCESS,NULL CONTROL},
IDC LASTBDTEMP,"lastbdtemp",NANFLT,N~NFLT,NULL_LEVEL,NULL_ACCESS,
NULL CONTROL } ,
IDC LASTP04,"lastpo4",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
IDC LASTNH3,"lastnh3",NANFLT,NANFLT,NULLILEVEL,NULL ACCESS,NULL CONTROL},
IDC: LASTPH,"lastph",NANFLT,NANFLT,NULL L~VEL,NULL A~CESS,NULL CONTROL},
IDC: B4LASTT,"b4lastt",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
IDC: B4LASTBDTEMP,"b4lastbdtemp",NANFLT,NANFLT,NULL LEVEL, NULL ACCESS,
NULL CONTROL } ,
IDC B4LASTP04,"b4lastpo4",NANFLT,NANFLT,NULL LEVEL, NULL ACCESS, NULL CONTROL
IDC:_B4LASTNH3,"b4lastnh3",NANFLT,NANFLT,NULL'LEVEL,NULL ACCESS, NULL CONTROL
IDC B4LASTPH,"b4lastph",NANFLT,NANFLT,NULL L~'VEL,NULL AUCESS,NULL CONTROL},
IDC LASTBD,"lastbd",NANFLT,NANFLT,NULL LEVEL,NULL ACC~SS,NULL CONTROL},
IDC:VLASTL,"lastl",NANFLT,NAN:FLT,NULL L'EVEL,NULL A(CESS,NULL CONTROL},
IDC: DT1,"dtl",NANFLT,NANFLT,NULL LEV~L,NULL ACC~SS,NULL CONTROL ,
IDC' DT2 , "dt2" , NANFLT, NANFLT, NULL~LEVEL, N'CTL,L ACCESS, NULL CONTROL ,
IDC: FAl,"fal",NANFLT,NANFLT,NULL LEVEL,NUI,L~ACCESS,NULL CONTROL ,
IDC FA2,"fat",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC:_FA3,"fa3",NANFLT,NANFLT,NULLrLEVEL,NULL~ACCESS,NULL-CONTROL ,

CA 02415685 2003-02-04
IDS'.: FB1,"fbl",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL ~~ONTROL ,
ID~~ FB2, "fb2" , NANFLT, NANFLT, a~LTLL LEVEL, NULL ACCESS, NULL CONTROL ,
IDC.~83,"fb3",NANFLT,NANFLT,L~UI,L LEVEL,NULL ACCESS,NULL CONTROL ,
IDC ~STDT1,"lastdtl",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC LASTDT2,"lastdt2",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULIJ CONTROL ,
IDC_LASTFA1,"lastfal",NANFLT,NANFLT,NULL~LEVEL,NULL ACCESS,NULL CONTROL ,
IDC LASTFA2,"lastfa2",NANFLT,NANFLT,NL1LL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC LASTFA3,"lastfa3",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDWLASTFB1,"lastfbl",NANFLT,NANFLT,NULL~_LEVEL,NULL ACCESS,NULIJ CONTROL ,
IDC LASTFB2,"lastfb2",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL ,
IDC LASTFB3,"lastfb3",NANFLT,NANFLT,NULL_~LEVEL,NULL ACCESS,NULL CONTROL ,
IDWUPDATESTATUS,"updatestatus",NANFLT,NANFLT,NULL LEVEL, NULL ACCESS,
NULL CONTROL},
{IDC LASTUPDATESTATUS,"lastupdatestatus",NANFLT,NANFLT,NULL_LEVEL,
NULL ACCESS,NULL CONTROL},
{IDC INITSTATUS,"initstatus",NANFLT,NANFLT,NULL LEVEL, NULL ACCESS,
NULL CONTROL},
IDC UNDOABLE,"undoable'°,NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL
CONTROL},
IDC LAUNCH,"LAUNCH",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
IDC CLOSE,"CLOSE",NANFLT,NANFLT,NULL LEVEL,NULL ACCESS,NULL CONTROL},
NAIVINT,"NANINT",NANFLT,NANFLT,NULLILEVEL,NULL~ACCESS,NULL_CONTROL},
/* pid2param: returns the parameter struct given the parameter id */
M4S1?ARAM *pid2param(M4SPARAMID pid) {
:int i = 0 ;
.'or (i = o; i r sizeof(m4sparameters)/sizeof(m4sparameters[0]); i++)
if (m4sparameters[i].pid =- pid) return(&m4sparameters[i]);
M BUGALERT("Bad parameter id.");
return(&m4sparameters [i-1] ) ;~
/* hid2name: returns name given the pid */'
char *pid2name(M4SPARAMID pid)
return(pid2param(pid)->name);
%* pid2low: return low field*/
double pid2low(M4SPARAMID pid)
rE~turn (pid2param (pid) - >low) ;
%* pid2low: return high field*,~
double pid2high(M4SPARAMID pid)
return (pid2param (pid) ->high) ;
/*~compressexponent: replaces first e0, a+0, or e-0 with e, a+, or e- */
/* !Vote: the extra 0 in numbers like 1e-9 introduced by sprintf's 1-09
encoding can be a problem (it doesn't fit into the field anymare)
.3o we replace e0*, e-0*, and a+0* with a*, e-*, and a+* */
char *compressexponent(char *numstring) {
char *e = strpbrk(numstring, "eE");
if (NULL =- e) /* no a or E in the string--leave as is */
else if ('0' -- a[1] && a[~] !_ '\0') /* for example, e09 */
lstrcpy(e+1, a+2);
else if ( (' -' -- a [1] ~ ~ ' +~' -= a [1] ) &&
(' 0' -- a [2] && a [3] ! _ ' \0' ? ) /* for example, e-09*/
lstrcpy(e+2, a+3);
return (numstring);
/*encodenumber: convert a number to a character string */

CA 02415685 2003-02-04
/* (5/28/94) Phil Gables reported that he noted that Windows became
anusable (VERY SLOW) when MACC4SSP was run on a system that had
S~~ rtscan Plus but no SmartCard. He also reported that
~ JR SMARTCARD TIMEOUT was always showing. This routine is used to
shortcircuit SCIL calls after the first ERROR SMARTCARD TIMEOUT; since
each such timeout takes 0.5 seconds and the timer messages come in
every 3 seconds, this gives Windows a "timeout breather" and is intended
to correct the problem of multiple SOIL 0.5 sec timeouts adding up to
VERY SLOW Windows response.
This routine relies on the fact that each time a WM TIMER message comes
in the sciinit() call is made and, if succesful, clears any old
timeout errors stored in the scierr field.
BOOL timedoutbefore(M4SENGINEID eid, SCIError *err)
if * (m4e [eid] . scierr c~DRTQM~O~TCARD TIMEOUT)
err - ERROR SMART ,
return(TRUE);
else
return(FALSE);
/* scigetdouble: retrieves value associated with an engine,parameter
from SSP*/


double scigetdouble(M4SENGINEID eid, M4SPARAMID pid) {


float f;


SCIError err = ERROR NONE;


if (m4e [eid] .local input)


f = ini2dbl(eid, pid);


else if (!timedoutbefore(eid, &err)) {


if (m4e [eid] . simulatessp)


simSCReadAnalogPvint(m4spointname(eid,pid), &f, &err);


else


SCReadAnalogPoint(m4spointname(eid,pid), &f, &err);


}aybestoreerror(eid, pid, err);


i:E (err == ERROR NONE)


return(decodedouble(encodefloat(f)));


/* the curious encode/decode above is a hack to simplify numericformatting*/


else


return (NANFLT);


/*scisetdouble: sends value to the SSP point associated with
engine,parameter*/


SCIError scisetdouble(M4SENGINEID eid, M4SPARAMID pid, double{
x)


float f = x;


SCIError err = ERROR NONE;


if (!timedoutbefore(eid, &err)) {


if (m4e[eid].simulatessp)


simSCWriteAnalogPoint(m4spointname(eid,pid), (const float) f, &err);


else
SCWriteAnalogPoint(m4spointname(eid,pid), (const float) f, err);


}aybestoreerror(eid, pid, err);


return(err);


/* ;scigetbit: retrieves flag associated with an engine,parameterfrom SSP */


BOOLPLUS scigetbit(M4SENGINEID eid, M4SPARAMID pid) {


BOOL bit;


SCIError err = ERROR NONE;



CA 02415685 2003-02-04
if (m4e [eid] .local input)
'bit = ini2int(eid, pid);
e1 " if ( ! timedoutbefore (ei.d, &err) )
(m4e [eid] . simulatessp)
simSCReadDigitalPoint(m4spointname(eid,pid), &bit, &err);
else
SCReadDigitalPoint(m4spointname(eid,pid), &bit, &err);
}aybestoreerror(eid, pid, err);
return ( (err =- ERROR NONE) ? bit . NANBOOL );
/* scisetbit: sends flag to the SSP point associated with an
engine,parameter*/
SCIError scisetbit(M4SENGINEID eid, M4SPARAMID pid, BOOL bit) {
SCIError err=ERROR NONE;
if (!timedoutbefore(eid, &err)) {
if (m4e [eid] . simulatessp)
simSCwriteDigitalPoint(m4spointname(eid,pid), bit, &err);
else
SCWriteDigitalPoint(m4spointname(eid,pid), bit, &err);
}aybestoreerror(eid, pid, err);
return(err); '
/*
scierrstr: returns the engine's error string; basically just the
SCIL error keywords except, sometimes, point specific info is added.
*/
char *scierrstr(M4SENGINEID eid) {
static char scierr[MAXSTRING+1];
switch (m4e [eid] . scierr) {
case ERROR NONE:
return("ERROR NONE");
case ERROR INVALID PROJECT NAME:
return("ERROR INVALID~PROJECT_NAME");
case ERROR PROJECT NOT FOU13D:
return("ERROR PROJECT NOT FOUND");
case ERROR SMARTSC~N PLUS NOT RUNNING:
return("ERROR SMARTS~'AN NOT_RUNNING");
case ERROR SMARTCA~D TIMEOUT:
return("ERROR SMARTC:ARD TIMEOUT"'.;
case ERROR POINT NAME NOT FOUND: /* SCIL errors that involve points*/
sprintf(scierr, "ERROR %s NOTFOUND",
m4spaintname(eid,m4e[eid].pidscierr));
return(scierr);
case ERROR DATA TYPE MISMATCH:
sprintf(scierr,~"ERROR %s's DATA TYPE",
m4spointname(eid,m4e[eidT.pidscierr));
return(scierr);
/* these cases should never t:appen (or else it's a bug): */
case ERROR LINK ALREADY ESTABLISHED:
return("BUG ALREADY LINKED");
case ERROR LINK NOT CURRENTLY ESTABLISHED:
return("~UG_N~T_L~NKED");
default
return("BUG UNRECOGNIZED ERROR");

CA 02415685 2003-02-04
/* ci"tle: returns the title bar. for the dialog box (acts as status line)*/
char ~itle(M4SENGINEID eid) {
static char titlebuf[MAXSTRING+1];
svprintf(titlebuf, "MACC4SSP Boiler%i (%s)", eid,
( ! m4e [eid] . running) ? "STOPPED"
m4e[eid].badchecksum ? "BADCHECKSUM!!!" .
scierrstr(eid) );
return(titlebuf);
/* getssprequest: loads data fields and returns requested action shown below:
*/
typedef enum ssprequest {
NOOPSSPREQUEST,
UPDATESSPREQUEST,
REINITSSPREQUEST,
UNDOSSPREQUEST,
OUTOFRANGESSPREQUEST,
NODATASSPREQUEST,
AMBIGUOUSSSPREQUEST
SSPREQUEST;
SSPREQUEST getssprequest(M4SENGINEID eid) {
BOOLPLUS update, reinit, undo;
double ph, po4, nh3, bdtemp;
if (!m4e[eid].actionsuptodate)/* avoids reading the same action twice */
return(NOOPSSPREQUEST); /* if last action was not cleared*/
else if (NANBOOL =- (update = scigetbit(eid, IDC UPDATE))
NANBOOL =- (reinit = scigetbit(eid, IDC REINIT))
NANBOOL =_ (undo = scigetbit(eid, IDC UNDO)))
return(NOOPSSPREQUEST);i'*can't read all action flags;wait til next timer*/
else if (update + reinit ~~ undo =- a) /* either no, ...*/
return(NOOPSSPREQUEST);
else if (update + reinit ~- undo > 1) { /* or several, actions requested */
M M4SERROR("Ambiguous request was ignored.");
return(AMBIGUOUSSSPREQUEST);
}lse'if (undo) /* undo has no arguments (no need to read pH, P04, etc.)*/
return(UNDOSSPREQUEST);
else if ( NANFLT =- (ph - scigetdouble(eid, IDC PH))
NANFLT =- (po4 - sci etdouble(eid, IDC P04))
(m4e [eid] . nh3enabled &~
NANFLT =_ (nh3 = scigetdouble(eid, IDC NH3)))
NANFLT =_ (bdtemp = scigetdouble(eid, IDC BDTEMP)) )
return(NODATASSPREQUEST); /* can't read required info; wait til next timer*
else
M ASSERT(update+reinit==1);
/* check/display range errors on each numerical input */
if (!inrange(ph, pid2low(IDC PH), pid2high(IDC~PH))) {
M M4SERROR2(rangeerror(IDC PH));
return(OUTOFRANGESSPR.EQUEST);
else if (!inrange(po4, pid2low(IDC P04), pid2high(IDC_P04))) {
M M4SERROR2(rangeerror(IDC P04))
return(OUTOFRANGESSPREQUEST);
else if (!inrange(bdtemp, pid2low(IDC BDTEMP), pid2high(IDC BDTEMP))) {
M M4SERROR2(rangeerror(IDC BDTEMP));
return(OUTOFRANGESSPREQUESfi);

CA 02415685
2003-02-04


else if (m4e
[eid] .
nh3enabled
&&


!inrangelnh3,
pidclow(IDC
NH3),pid2high(IDC
NH3)))


_
M M4SERROR2(rangeerror(IDC
NH3));


. return(OUTOFRANGESSPREQUES~');



else { /* so... */
all inputs
in allowed
range


m4e[eid].blr.ph request
- ph; /* */
store data
needed to
service


m4e [eid]
. blr. po4
- po4 ;


if (m4e [eid]
.nh3enabled)


m4e [eid]
.blr.nh3
- nh3;


m4e[eid].blr.bdtemp
= bdtemp;


return(update
? UPDATESSPREQUEST
. REINITSSPREQUEST);


/*encodeflags:
encodes
status code
flags as
a string
*/


char *encodeflags(M4SENGINEID
eid)


static char
flagtext[MAXSTR.ING+1];


flagtext - (m4e [eid] .blr.updatestatus&E INDETERMINATE) :' ' ;
(O] ? ' 1.'


flagtext[1] - (m4e [eid].blr.updatestatus&E BOX UNREACHABLE) 2'~' ';
? '


f lagtext ' 3' : '
[2 ] - (m4e ' ;
[eid] .
blr . updatestatus&E
OUTOFBOX
TOOLONG)
?


flagtext _
(3] - (m4e ' 4' :'
[eid] .blr.updatesta-tus&E ' ;
POINT UNREACHABLE)
?


flagtext[4]= 'S':' ';
(m4e(eid].blr.updatestatus&E
BOX UNMAINTAINABLE)
?


flagtext[5]= ?'6':'_';
(m4e[eidl.blr.updatestatus&E-POI~3T

UNMAINTAINABLE)


flagtext
(6] _


(m4e [eid]
.blr.t-m4e
[eid] .blr.lastt~m4e
[eid] .blr.max_sample-interval)



., , .~ .
, ,


flagtext[7] ' ';
- (m4e[eid].blr.updatestatus
& E NOTUPDATED)
? '8':
~


NCONSISTENT ' 9' : '
P04 ) :' ' ;
f lagtext
(8 ] _ (m4e
(eid] .
blr . updatestatus
& E 3


PH) ? 'A':',';
flagtext[9]
- (m4e[eid].blr.updatestatus
& E INCONSISTEN~


_
';
flagtext(10]
- (m4e(eid].blr.initstatus!=E
I~ITOK)
? 'B' '


_
' ;
f:lagtext
(11] - m4e
(eid] .bad.checksum
? 'C'-'


,-
flagtext
[12] _'
\0' ;


return (
f lagtext
) ;


/* :logfile: returns the name of the MACC4SSP log file for a given engine */
chair *logfile(M4SENGINEID eid) {
r~aturn(lstrcat(lstrcat(m4spath(),nameprefix(eid)),".LOG"));
/* fileexists: checks if a file exists. */
BOO:L fileexists (char *fname) {
unsigned attr=0;
return(( doe_getfileattr(fname,&attr) -- 0)?TRUE: FALSE);
/* logaction: used to log updates, reinits, undos, STARTS, and STOPS */
void logaction(M4SENGINEID eid,
M4SPARAMID pid) /* indicates action: update, reinit, etc.*/
int hFile = -1;
long lastbyte = -1;
if (!m4e[eid].logtofile) /* skip if logging is disabled */
else if (fileexists(logfile(eid) ) &&
-1 =- (hFile = lopen(logfile(eid), OF READWRITE)))
M M4SERROR("Cannot open log file");

CA 02415685 2003-02-04
else if (!fileexists(logfile(eid)) &&
-1==(hFile = _lcreat(logfile(eid), 0)))
M M4SERROR("Cannot create log file");
e1 ~~ if (-1 =_ (lastbyte = _llseek(hFile, 0, 2)))
_-i M4SERROR("Cannot move to end of log file");
else ~ /* print out header (only if file empty) and next log file line */
static char tobeprinted[5*MAXSTRING+1];
if (lastbyte a 1) { /* file not empty, assume it already has header*/
lstrcpy(tobeprinted, °");
else { /* empty file, add header to it */
sprintf(tobeprinted,
"°s-4s,%-6s,%-20s,%-14s,%-14s,"
"%-9s,%-9s,%-9s,%-9s,%-9s,%-9s,%-9s,%-9s,%-9s,%-9s,%-9s,%-9s",
"Sim?","Action","DateTime","HoursSince1970","flags", "pH", "P04",
"bdtemp","NH3", "fal", "fat", "fa3", "fbl", "fb2", "fb3",
"dtl", "dt2");
/* append any to be printed info to the (optional) header created above */
sprintf(tobeprinted+lstrlen(tobeprinted),
"\n%-4s,%-6s,%-20s,%-14f,%-14s,",
m4e[eid].simulatessp ? "Yes" . "No", pid2name(pid),
m4sdatetime(timeinhours()), m4e[eid].blr.t,
encodeflags(eid));
sprintf(tobeprinted+lstrlen(tobeprinted),
"%-9f,%-9f,%-9f,%-9f,%-9f,%-9f,%-9f,%-9f,%-9f,%-9f,%-9f,%-9f",
m4e [eid] .blr.ph., m4e [eid] .blr.po4,
m4e [eid] .blr.bdtemp, fixnandbl (m4e [eid] .blr.nh3, 0.0) ,
m4e [eid] .blr. fa [0] , m4e [eid] .blr. fa [1] ,
m4e [eid] .blr. fa [2] , m4e [eid] . blr. fb [O] , m4e [eid] . blr. fb [1] ,
m4e [eid] .blr. fb [2] , m4e [eid] .blr.dt [0] , m4e [eid] .bl.r.dt [1] ) ;
if ( lwrite(hFile, tobeprinted, lstrlen(tobeprinted)) <= 0)
M_I~4SERROR("'Cannot write to log file");
~f (hFile !_ -1 &~ -1 =- lclose(hFile))~
M M4SERROR("'Cannot close log file");
/*maybeclearactions: clear action flags if not already cleared */
BOO:L maybeclearactions(M4SENGINEID eid)
if (!m4e[eid].actionsuptodate) {
if (m4e [eid] .local input) {
int2ini(eid, IDC UPDATE, FALSE);
int2ini(eid, IDC UNDO, FALSE);
int2ini(eid, IDC~REINIT, FALSE);
m4e[eid].actionsuptodate = TRUE;
}lse {
m4e[eid].actionsuptodate =(scisetbit(eid,IDC UPDATE,FALSE)==ERROR NONE &&
scisetbit(eid,IDC UNDO, FALSE)==ERROR NONE &&
scisetbit(eid,IDC REINIT,FALSE)==ERROR NONE);
return(m4e[eid].actionsuptodate);
/* maybesetflags: sets flags in SSP that correspond to the updatestatus */
/* note: order in which flags are set matters due to a kludge used
in simSCWriteDigitalPoint() */
BOOL maybesetflags(M4SENGINEID eid) {
BOOL fl,f2,f3,f4,f5,f6,f7,f8,f9,f10,fl1,f12;
#de:fine M BIT(eid,flag) ((m4e[eid].blr.updatestatus & (flag))!=0)

CA 02415685 2003-02-04
iii (!m4e[eid].flagsuptodate) { /* update SSP flags if required */
if (m4e [eid] .local input) {
f7=(ERROR NONE==scisetbit.(eid,iDC FLAG7,
m4e[eidT.blr.t-m4e[eid].blr.lastt>m4e[eid].blr.max sample interval));
.12=(scisetbit(eid,IDC_FLAG12,m4e[eid].badchecksum)==ERROR NONE);
m4e[eid].flagsuptodate = (f7 &~ f12);
else
fl=(scisetbit(eid,IDC FLAG1, M BIT(eid,E INDETERMINATE))==ERROR NONE);
f2=(scisetbit(eid,IDC FLAG2, M BTT(eid,E~BOX UNREACHABLE))==ERROR NONE);
f3=(scisetbit(eid,IDC FLAG3, MuBIT(eid,E OUT~FBOX TOOLONG))==ERROR NONE);
f4=(scisetbit(eid,IDC_FLAG4, M BIT(eid,E~POINT UNREACHABLE))
-=ERROR NONE);
f5=(scisetbit(eid,IDC FLAGS, M_BIT(eid,E_BOX_UNMAINTAINABLE))
-=ERROR NONE);
f6=(scisetbit(eid,IDC-PLAG6, M BTT(eid,E~POINT-CTNMAINTAINAHLE))
-=ERROR NONE);
f7=(ERROR NONE==scisetbit:(eid,IDC FLAG7,
m4e[eidT.blr.t-m4e[eid].blr.lastt>m4e[eid].blr.max sample interval));
f8=(scisetbit(eid,IDC FLAGB, M BIT(eid,E NOTUPDATED)T==ERROR NONE);
f9=(scisetbit(eid,IDC FLAG9, M BIT(eid,E~INCONSISTENT P04))==ERROR NONE);
f10=(scisetbit(eid,IDC FLP.G10,M BIT(eid,>~ INCONSISTENT PH))==ERROR_NONE);
fll=(scisetbit(eid,IDC_FLAGll,m4'eCeid].blr.initstatus!=E_INITOK)
-=ERROR NONE);
f 12= ( scisetbit (eid, IDC FL,AG12 , m4e Ceid] . badchecksum) ==ERROR NONE) ;
m4e [eid] . f lagsuptodate~= ( f l && f2 ~s~ f 3 && f4 ~& f 5 && f 6!&& f 7 &&
f 8 &&
f9 && f10 && fll && f12);
rE~turn (m4e (eid] . flagsuptodate) ;
%*setbuttontext: sets the appropriate text string for an engine's button */
/* (assumes that IDC ENGINEO, IDC ENGINE1, ... ids are sequential) */
void setbuttontext(HWND hDlg, M4SENGINEID eid) {
static char buttontext [MAXSTRING+1] ;
sprintf(buttontext, "%sBoiler%i%s",
m4e[eid].running ? "*" . "", eid, m4e[eid].running ? "*" . "");
s:2ctrl(GetDlgItem(hDlg, IDC ENGINEO+eid), buttontext);
/*updateform: updates those fields which change while engine is running */
void updateform(HWND hDlg, M4SENGINEID eid) {
if ( !m4e [eid] . running)
s2ctrl(GetDlgItem(hDlg,IDC DATETIME), "");
s2ctrl(GetDlgItem(hDlg,IDC~_AFEED), "");
s2ctrl(GetDlgItem(hDlg,IDC BFEED), "");
s2ctrl(GetDlgItem(hDlg,IDC STATUSFLAGS), "");
else { /* update the form from the current in-memory info */
if ( !m4e [eid] .local input) {
s2ctrl(GetDlgItem(hDlg, IDC_PH), encodeshortfloat(m4e[eid].blr.ph));
s2ctrl(GetDlgItem(hDlg, IDC. P04), encodeshortfloat(m4e[eid].blr.po4));
if (m4e [eid] . nh3enabled)
s2ctrl(GetDlgItem(hDlg, IDC NHS), encodeshortfloat(m4e[eid].blr.nh3));
s2ctrl(GetDlgItem(hDlg, IDC_B~TEMP),
encodeshortfloat(m4e~:eid] blr.bdtemp));
s2ctrl(GetDlgItem(hDlg, IDC_BD), encodefloat(m4e[eid].blr.bd));
s2ctrl(GetDlgItem(hDlg, IDC_BDMIN), encodefloat(m4e[eid].blr.bdmin));
s2ctrl(GetDlgItem(hDlg, IDC BDMAX), encodefloat(m4e[eid].blr.bdmax));
s2ctrl(GetDlgItem(hDlg, IDC FWNA),
encodefloat (naleak2fwna (m4e [eid] . blr. 1, m4e [eid] . fwflow) ) ) ;

CA 02415685 2003-02-04
s2ctrl(GetDlgItem(hDlg, IDC MINFwNA),
encodefloat(naleak2fwna(m4e[eidl.blr.naleakmin,m4e[eid].fwflow)));
s2ctrl(GetDlgItem(hDlg, IDC MAXFWNA),
~.~ncodefloat (naleak2fwna (m4e [eid] .blr.naleakmax,m4e [eid] . fwflow) ) ) ;
trl(GetDlgItem(hDlg, IDC DP04), encodefloat(m4e[eid].blr.dpo4));
s~ctrl (GetDlgItem(hDlg, II)ClDPH) , er_codefloat (m4e [eid] .blr.dph) ;~ ;
/* note: macc4.c~s a init() may fill in min/max bd and naleak, dpo4 and
dph, hence above Iines to update screen to reflect such fill-ins */
s2ctrl(GetDlgItem(hDlg,IDC DATETIME), m4sdatetime(m4e[eid],blr.lastt));
s2ctrl(GetDlgItem(hDlg,IDC' AFEED),
encodef loat ( ( float ) e-_afeed ( &m4e [eid] , blr) ) ) ;
s2ctrl(GetDlgItem(hDlg,IDC BFEED),
encodefloat ( (float) a bfeed(&m4e [eid] .blr) ) ) ;
s2ctrl(GetDlgItem(hDlg,ID(~_STATUSFLAGS), encodeflags(eid));
/* predictphpo4: computes and displays MACC4 predicted pH/P04. pH is computed
at room temperature and without ammonia (NH3=0).*/
void predictphpo4(M4SENGINEID eid, /* id of boiler controller/model */
double deltas /* number of hours into the future to look */
static char predictstr[2*MAXSTRING+1];
E BOILER blr = m4e[eid].blr; /*make a copy so we can change with impunity*/
blr.t = m4stimeinhours(eid) + deltat;
blr.bdtemp = ROOMTEMPERATUREDEGREESF; /* Uses room temperature and */
blr.nh3 = 0.0; ;'* no NH3 to be compatible/comparable with */
~'* pH setpoint (which is so defined) */
blr.po4 = e_po4model(&blr); /* set current P04, pH to model projections*/
blr.ph - erphmodel(&blr);
sprintf(predictstr,
"The %s MACC4 forecast: P04=%.5g, pH (no NH3; %g F)=%.5g Na:P04=%.5g ",
m4sdatetime(blr.t), blr.po4, ROOMTEMPERATUREDEGREESF,
blr.ph, a congruencyratio(&blr));
clearmarqueZ);
marque(predictstr); /* display the forecast */
/*oktostopmessage:confirmation text message for stopping a boiler controller*/
char *oktostopmessage(M4SENGINEID eid) {
static char oktostoptext[MAXSTRING+1];
sprintf(oktostoptext, "OK to stop MACC4SSP Boiler%i?", eid);
return(oktostoptext);
/*
closewarningmessage: warning. message if they try to shut down MACC4
while boilers are still running/being controlled */
char *closewarningmessage(int numberrunning) {
char boilerlist[MAXSTRING+1]={0~;
static char closewarningtext[4*MAXSTRING+1];
#define M BOILERTEXT(eid) (m4e[eid].running ? (", Boiler" #eid) : "")
sprintf-(boilerlist, "%s%s%s%s%s%s%s°ss%s%s", M BOILERTEXT(0), M
BOILERTEXT(1),
M BOILERTEXT(2), M BOILERTEXT(3), M B~SILERTEXT(4), M B~ILERTEXT(5).
M BOILERTEXT(6), M_BOILERTEXT(7), M HOILERTEXT(8), M BOILERTEXT(9));
sprintf(cIosewarningtext,
"%s %s still running. If you close MACC4SSP now, the chemical feed rates for "

CA 02415685 2003-02-04
"%s will not be updated until you re-launch MACC4SSP. tTnless MACC4SSP is "
"re-~launched promptly, this could result in poor chemical control.",
boilerlist+2, (numberrunn:ing > 1) ? "are" . "is",
°°°imberrunning > 1) ? "tha_se boilers" . "this boiler");
return(closewarningtext);
/* setfocusnextboiler: sets focus to the next boiler's selection button*/
/* (assumes boiler selection button id's are sequential) */
void setfocusnextboilerbutton(HWND hDlg, M4SENGINEID eid) {
3etFocus(GetDlgItem(hDlg,IDC.'~ENGINEO+(eid+1)%(MAXENGINEID+1)));
/* WndProc: handles all messages Windows sends to our main window/dialog box
*/
long FAR PASCAL WndProc(HWND hDlg, WORD message, WORD wParam, LONG lParam)
static int numberrunning = 0;
static BOOL windowlocked = TRUE;
HWND hCtrl - LOWORD(lParam); /* parse messages to controls */
lilt ctrlmsg = HIWORD (lParam) ;
lilt eid = 0 ;
switch(message) {
case WM CREATE:
/* _
l~lote that SSP need not be running at this point; if SSP communications are
down for an extended period we "ride the clutch" (c.f WM TIMER
case) until SSP communications are restored and sciinitT)==ERROR NONE.
*/
for (numberrunning = 0, eid = MINENGINEID; eid <= MAXENGINEID; eid++){
m4e[eidJ.running=fixnanint(ini2int(eid,IDC-RUNNING), FALSE);
if (m4e[eidJ.running) {
numberrunning++;
if (ini2engine(eid)) { /* bad checksum -- alert them */
M_M4SERROR(
"BAD CHECKSUM! Cannot restore state of a running engine!");
logaction(eid, IDC LAUNCH);
m4e[eidJ.currentevent = nullevent;
updatessppoints(ei.d);/*brings SSP points to well defined state*/
/*.current engine id is always stored in the default engine's INI file */
currentengineid=fixnanint(ini2int(DEFENGINEID,IDC CURRENTENGINEID),
DEFENGINEID);
M ASSERT(currentengineid >= MINENGINEID);
M ASSERT(currentengineid <= MAXENGINEID);
i~ (currentengineid < MINENGINEID ~~ currentengineid > MAXENGINEID)
currentengineid = DEFENGINEID; /* defensive programming */
windowlocked = TRUE; %* user must enter password to change/close */
break;
case WM SETFOCUS:
if (windowlocked)
SetFocus(GetDlgItem(hDlg, IDC~PASSWORD));
break;
~r

CA 02415685 2003-02-04
case WM COMMAND:
awitch(wParam) {
case IDC LOGTOFILE: /* record change in log status in ini file*/
m4e[currentengineid].logtofile = getcheckboxstate(hCtrl);
int2ini(currentengineid, IDC_LOGTOFILE,
m4e(currentengineid] logtofile);
break;
case IDC SIMULATESSP:
m4e[currentengineid].simulatessp = getcheckboxstate(hCtrl);
int2ini(currentengineid, IDC SIMULATESSP,
m4e[currentengineid]rsimulatesap);
break;
case IDC LOCALINPUT:
m4e[currentengineid].localinput = getcheckboxstate(hCtrl);
int2ini(currentengineid, IDC LOCALINPUT,
m4e[currentengineid]~localinput);
constraininputs(hDlg, currentengineid, windowlocked);
s2ctrl(GetDlgItem(hDlg, IDC_POINTNAMES),pointnames(currentengineid));
break;
case IDC NH3ENABLED:
m4e[currentengineid].nh3enabled = getcheckboxstate(hCtrl);
int2ini(currentengineid, IDC NH3ENABLED,
m4e (currentengineid] ~nh3-enabled) ;
if (!m4e[currentengineid].nh3enabled) { /*if NH3 not used*/
s2ini(currentengineid, IDC NH3, NANSTRING); /*clear in INI file*/
s2ctrl(GetDlgItem(hDlg, IDC_NH3), NANSTRING);/*as well as on form*/
}onstraininputs(hDlg, currentengineid, windowlocked);
s2ctrl(GetDlgItem(hDlg, IDCiPOINTNAMES),pointnames(currentengineid));
break;
case IDC UPDATE:
case IDC UNDO:
case IDC REINIT:
M ASSE~tT(m4e[currentengineid].localinput);
int2ini(currentengineid, wParam, TRUE);
SendMessage(hDlg, WM_TIMER, M4STIMERID, NULL);
break;
case IDC LOCKWINDOW:
windowIocked = TRUE;
s2ctrl(GetDlgItem(hDlg, IDC PASSWORD),""); /* clear password */
constraininputs(hDlg, currentengineid, windowlocked);
if (windowlocked)
SetFocus(GetDlgItemChDlg, IDC_PASSWORD));
break;
case IDC PASSWORD:
if (windowlocked && lstrcmp(ctrl2s(hCtrl), "ERIC4") _= 0)
windowlocked = FALSE;
s2ctrl(GetDlgItem(hDlg, IDC PASSWORD),""); /* clear password */

CA 02415685 2003-02-04
constraininputs(hDlg, currentengineid, windowlocked);
SetFocus(hDlg);
break;
case IDC FORECAST:
M ASSERT(m4e[currentengineid].running);
predictphpo4(currentenc~ineid,
max(O.O,fixnandbl(im2dbl(currentengineid, IDC FORECASTTIME),0.0)));
break;
/* LVumeric only entry restrictions and on-the-fly initialization file
backup of edit control entries:
Invariants:
1) All edit control entries agree with those stored in the initialization
file, except possibly changes made to the edit control that has the focus.
2) Edit controls below either contain a valid number, or have the
focus (caret or text cursor) and contain a valid numeric prefix (.,
. are all examples of valid numeric prefixes that aren't valid numbers)
These invariants are assumed r_o hold initially (ini2dlg() call in IDC
LOADFORM
comrnand message, sent to main window in WinMain, guarantees this).
The first invariant is maintained by writing the final numeric value in the
edit control to the initialization file when the control loses the
focus, unless this edit control text has not changed.
The second invariant is maintained as changes are made to the edit controls
(the:se changes generate EN CHANGE messages') by the following rules:
1) Function forcenumeric() is used to either truncate or totally eliminate
non-numeric entries for any changes that lead to non-numbers in
those controls that do not have the focus. forcenumeric() is also applied
t:o the control when it just loses the focus (via the EN~KILLFOCUS message).
2) When the control has the focus, the most recently validated text is
z:ememb~red and the control text is restored to this value if any changes
result in an entry that is not a valid numeric prefix. This is mostly
intended to reject user entry errors (e. g. pressing the 'A' key).
*/
case IDC MINFWNA: case IDC MAXFWNA: case IDC_FWNA: case IDC BDMAX:
case IDC BDMIN: case IDC M~ case IDC BDTEMP: case IDC DPHSETPOINT:
case IDC PHSETPOINT: case IDC DPH; case IDC_PH: case ~DC_MAXP04:
case IDC MINP04: case IDC P04SETPOINT: case IDC DP04:
case IDC P04: case IDC SPCA: case IDC SPGB: case IDC P04B:
case IDC FBDEF: case IBC FBMAX: case ADC FBMIN: case IDC RATIOB:
case IDC RATIOA: case IDC P04A: case IDC FADEF:
case IDC FAMAX: case IDC FAMIN: case IDC BD:
case IDC FORECASTTIME: case IDC~FWFLOW: case IDC NH3:
switch(ctrlmsg) {
static HWND currentctrl = NULL; /* handle to control in focus*/
static BOOL etrlchan ed = FALSE; /* did control in focus change?*/
static char lasttext~MAXSTRING+1]; /* contains last valid entry */
/* in current control */
case EN SETFOCUS: /* entering the edit control */
ctrlcfianged = FALSE;

CA 02415685 2003-02-04
currentctrl = hCtrl;
lstrcpy(lasttext,ctrl2s(hCtrl)); /* assumes what i.s in there*/
break; !* is a valid number */
case EN_C~NGE: /* contents of edit control changed*/
if (hCtrl != currentctrl) { /* if not in focus, force numeric*/
if (!isvalidnumber(ctrl2s(hCtrl))) {
s2ctrl(hCtrl, NANSTRING);
s2ini(curreaztengineid, wParam, NANSTRING);
M M4SERROR(
"Attempt to place,a non-number :in a numeric .field--entry cleared.");
else if (!inrange(decodedouble(ctrl2s(hCtrl)),
pid2low(wParam),
pid2high(wParam)))
s2ctrl(hCtrl, NANSTRING);
s2ini(currentengineid, wParam, NANSTRING);
M M4SERROR.(rangeerror(wParam));
else if (!isnumericprefix(ctrl2s(hCtrl))) {/*in focus&invalid*/
long lastpt = SendMessage(hCtrl, EM GETSEL, 0, C);
/*next line assumes single character insertion cause3 last text change*/
/*(otherwise the location of point will be unintuitive--no great harm)*/
lastpt = MAKELONG(max(LOWORD(lastpt)-1,0),
max(HIWORD(lastpt)-1,0));
s2ctrl(hCtrl,lasttext); /* restore previously validated text*/
SendMessage(hCtrl, EM SETSEL, 0, lastpt); /* and position */
M_M4SERROR("Numeric input required.");
else { /* vali.d new input: remember that control changed*/
ctrlchanged = TRUE;
lstrcpy(lasttext,ctrl2s(hCtrl)); /* store validated input*/
break;
case EN KILLFOCUS: /* exiting the edit control */
if (ctrlchangedl {
if (!isvalidnumber(ctrl2s(hCtrl)))
s2ctrl(hCtrl, "");
M_M4SERROR(q'Incomplete numeric entry cleared.'");
}1se if (!inrange(decodedouble(ctrl2s(hCtrl)),
pid2low(wParam),
pid2high(wParam)))
s2ctrl(hCtrl, NANSTRING>;
M_M4SERROR(rangeerror(wParam));
ctrl2ini(currentengineid, wParam, hCtrl);/* save the change*/
/* in the INI file*/
currentctrl = NULL;
break;
default:
break;
break;
case IDC LOADFORM: /* Use in lieu of WM INITDIALOG--there's no dlgproc*,
for (eid=MINENGINEID; eid <= MAXENGIN~'ID; eid++)

CA 02415685 2003-02-04
setbuttontext(hDlg, eid):
wParam = IDC_ENGINEO + currentengineid;
/* no break - emulates clicking current engine's button */
case IDC ENGINEO:
case IDC ENGINE1: case IDC ENGINE2: case IDC ENGINE3:
case IDC ENGINE4: case IDC~ENGINE5: case IDC ENGINE6:
case IDC ENGINE7: case IDC ENGINE8: case IDC ENGINES:
/* next Tine assumes that above ids are sequential */
currentengineid = wParam - IDC ENGINEO;
M ASSERT(currentengineid >= MINENGINEID):
M ASSERT(currentengineid <= MAXENGINEID);
int2ini(DEFENGINEID~, IDC CURRENTENGINEID, currentengineid);
ini2dlg(hDlg, currentengineid); /* load form from INI file */
updateform(hDlg, cu.rrentengineid);
SetWindowText(hDlg, r_itle(currentengineid));
constraininputs(hDlg, currentengineid, windowlocked);
/* allow for quickly reviewing each configuration in turn via the space bar:*/
setfocusnextboilerbur_ton(hDlg, currentengineid);
break;
case IDC START:
M ASSERT(!m4e[currentengineid].running);
m4e [currentengineid] . blr = *e undef izied,boiler ( ) ;
ini2engine(currentengineid);
m4e[currentengineid].starttime = timeinhours();
m4e[curre.ntengineid].lasteventtime =
m4e[currentengineid].blr.t = m4stimeinhours(currentengineid);
m4e[currentengineid].eventid = 0;
if ( NANFLT == m4e[currentengineid].fwflow ) {
M M4SERROR("Feedwater flow rate must be specified.");
SetFocus(GetDlgltem(hDlg, IDC FWFLOW));
else if ( m4e[currentengineid].hh3enabled &&
NANFLT == m4e[currentengineid].blr.nh3 ) {
M M4SERROR("Ammania must either be specified~or disabled.");
SetFocus(GetDlgItem(hDlg, IDC NH3));
else if (E INITOK != a init( &m4e[currentengineid].blr ) )
M M4SERR~R(initerr[m4e[currentengineid].blr.initstatus].msg);
SetFocusiGetDlgItem(hDlg, /* position to error related field */
initerr [m4e [currentengineid] .blr. initstatus] . field) ) ;
else (
m4e [current engine id] . running .= TRUE;
constraininputs(hDlg, currentengineid, windowlocked);
SetFocus(GetDlgItem(hDlg,IDC STOP));
updatessppointsl:currentengineid);
m4e[currentengineid].scierr = ERROR_NONE;
m4e[currentengineid].currentevent = nullevent;
engine2ini(currentengineid);
logaction(currentengineid, IDC START);
SetWindowText(hDlg, title(currentengineid));
setbuttontext(hDlg, c~.lrrentengineid);
clearmarque();
M M4SINF0("");
numberrunning++;
break;
case IDC STOP:
M ASSERT(m4e[currentengineid].running);
i~ (IDOK== MessageBox(hDlg, oktoatopmessage(currentengineid),
~'MACC4SSP Confirmation",

CA 02415685 2003-02-04
MB OKCANCEL~MB_ICONQUESTION)) {
m4e[currentengineid].running = FALSE;
constraininputs(hDlg, currentengineid, windowlocke<i);
SetFocus(GetDlgItem(hDlg,IDC START));
int2ini(currentengineid,IDC RUNNING, m4e[currentengineid].running);
logaction(currentengineid, IDC STOP);
updateform(hDlg, currentengineid);
SetWindowText(hDlg, title(currentengineid));
setbuttontext(hDlg, currentengineid);
numberrunning--;
break;
break;
case WM TIMER:
if (numberrunning > 0) {
for (eid=MINENGINEID; eid<=MAXENGINEID; eid++)
if (m4e [eid] . running) {
SCIError sciiniterr = sciinit(eid);
if (ERROR NONE==sciiniterr && !m4e [eid] .badchecksurrO
/*link to SmartCard inititialized OK and engine is running and
we don't have a bad checksum (__> undefined controller state) */
m4e[eid].blr.t = m4stimeinhours(eid); /* update current time */
if (m4e [eid] . simulatessp && ! m4e Ceid] . localinput'~
simulateoperator(eid); /* simulate operator updates, etc. */
switch (getssprec~uest (eid) ) {
HCURSOR holdcursor;
case NOOPSSPREQUEST: /* no action requeste,i */
break; '
case UNDOSSPREQUEST:
holdcursor = SetCursor(LoadCursor(NULL,Iii_W,~~IT));
if (E INITOK != m4e[eid] .blr.initstatus
m4stimeinhours (eid) > m4e [eid] .blr.lastt~-UNDO INTERVAL
! a undo ( ~m4e [eid] . blr) )
M.M4SERROR("Could not UNDO");
updatessppoints(eid);
engine2ini (e:id) ;
logaction(eid, IDC UNDO);
SetCursor(holdcursor);
break;
case UPDATESSPREQUEST:
holdcursor = SetCursor(LoadCursor(NULL,IDC WAIT));
if (E INITOK == m4e[eid].blr initstatus)
if Te update (&m4e [eid] .blr) & E NOTUPDATED)
M M4SERROR("Update failed.");
else if (m4e[eid].blr.updatestatus != E UFDATEOK)
M M4SERROR("Update warning--check flags.");
updatessppaints(eid);
engine2ini(eid);
logaction(eid, IDC UPDATE);
SetCursor(holdcursor);
break;
case REINITSSPREQUEST:

CA 02415685 2003-02-04
holdcursor = SetCursor(LoadCursor(NULL,IDC WAIT));
m4e[eid].blr.bd = E NAN; /* set bd, na leak to their */
m4e[eid].blr.l = E NAN; /* MACC4 determined defaults*/
if (e init(&m4e[eidT.blr)!=E INITOK)
M M4SERROR("REINIT Failed.");
updatessppoints(eid);
engine2ini(eid);
logaction(e.id, IDC REINIT);
SetCursor(ho7.dcursor);
break;
case NODATASSPREQUEST:
M_M4SERROR(
"Could not read required data to service request ");
/* no break */
case OUTOFRANGESSPREQUEST: /* numeric parameters out of range*/
case AMBIGUOUSSSPREQUEST: /* multiple requests--clear them */
m4e[eid].actionsuptodate = FALSE;
break;
f (E INITOK =- m4e [eid] . blr . initstatus )
scisetdouble(eid,IDC AFEED,e afeed(&m4e~eid].blr));/vset feed*/
scisetdouble(eid,IDC BFEED,e-bfeed(&m4e[eid].blr));/*pumps */
if ( !m4e [eid] .local input) { -
scisetdouble(eid,IDC BD, m4e[eid].blr.bd);
scisetdouble(eid,IDC FWNA,
naleak2fwna (m4e [ei3] .blr.l,m4e [eid] . fwflow) ) ;
scisetdouble(eid,IDC RATIO,
e-congruencyratio(&m4e[eid].blr));
} maybeclearactions(eid); /* clear action flags */
if (ERROR NONE =- sciiniterr) {
maybesetflags(eid); /* service flag setting requests */
scifini(eid); /* close link to SSP */
} %* end of "if running" */
} } /* end of looping over each engine id */
if (m4e[currentengineid].running) { /* make form reflect any changes*/
updateform(hDlg, currentengineid); /* due to operator inputs/actions */
SetWindowText(hDlg, title(currentengineid));
}eturn(0) ;
case WM CLOSE:
if (windowlocked) { /* cannot close locked window */
M M4SERROR("You must enter the password before closing MACt'4SSP");
return(0);
}
else if (numberrunning > 0 &&
IDOK != MessageBox(hDlg, closewarningmessage(numberrunning),
"MACC4SSP Warning", MB OKCANCEL~MB_ICON~:AND))
return(0); /* short circuit close *%
else { /* log the user's CLOSE action for each running boil.en */
for (eid=MINENGINEID; eid <= MAXENGINEID; eid++)
if (m4e [eid] . running)
logaction(eid, IDC CLOSE);
} /* fall through to ordinary Windows WM_CLOSE */
break;

CA 02415685 2003-02-04
case WM DESTROY:
KillTimer(hDlg,, M4STIMERID);
PostQuitMessage(0);
" return ( 0 ) ;
default:
break;
return DefWindowProc (hDlg, message, wParam, lParam);
/* WinMain: uses a dialog box as the main window as per the HEXCALC example*/
/* program from Petzold's book "Programming Windows" (Microsoft Preas) */
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wndclass;
HC:URSOR holdcursor;
hmainwin = NULL;
lpszCmdLine = lpszCmdLine; ;'* to supress compiler warning */
if: (hPrevInstance)
{ M M4SFATALERROR("MACC4SSP already running; switch to MACC4SSP instead.");
return (0);
else
{ /* register the class used by our dialog box/main window */
wndclass.style = CS HREDRAW ~ CS VREDRAW;
wndclass.lpfnWndProc = (WNDPROC)~WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = DLGWINDOWEXTRA;
wndclass.hlnstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(MACC4SSP_I~ON));
wndclass.hCursor = LoadCursor(NULL,IDC ARROW);
wndclass.hbrBackground = COLOR WINDOW+1;
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = M4SMODULENAME;
RegisterClass(&wndclass);
holdcursor = SetCursor(LoadCursor(NULL,IDC WAIT));
hrnainwin=CreateDialog(hInstance, MAKEINTRESOURCE(MACC4SSP DIALGG), 0, NULL);
SetCursor (holdcursor) ;
if (hmainwin =~ NULL)
M M4SFATALERROR("MACC4SSP fatal error: cannot create main window.");
return(0);
}:Lse if (!SetTimer(hmainwin, M4STIMERID, DEFTIMERINTERVAL, NULL))
M M4SFATALERROR("MACC4SSP fatal error: cannot create Windows tim{r");
return(0);
}:Lse
ShowWindow(hmainwin, nCmdShow);
SendMessage(hmainwin, WM COMMAND, IDC LOADFORM, 0); /* loads d:g box */
/* ~~annot do this in WM~CREATE because Windows creates dialog box controls*/

CA 02415685 2003-02-04
/* AFTER it sends WM CREATE; plays the role WM INITDIALOG usually ~.rould */
/* if we were using an ordinary DlgProc (we are using a WndProc in:atead)*/
while (GetMessage(&msg, NULL, 0, 0))
{f (!IsDialogMessage(hmainwin, &msg))
TranslateMessage(&msg);
DispatchMessage(&msg);
return (msg.wParam);

CA 02415685 2003-02-04
char *encodenumber(double x, const char *fmt) {
static char encodeddouble[MAXSTRING+1];
~~ (x =- NANFLT)
st{cpy(encodeddouble, NANSTRING);
else
sprintf(encodeddouble, fmt, x);
compressexponent(encodeddouble);
return(encodeddouble);
/*en.codedouble: convert a double-with full precision--to a character string
*/
char *encodedouble(double x)
return(encodenumber(x,DOUBLEPRECISIONFORMAT));
/* d.ecodedouble: convert a character string to a double */
double decodedouble(char *s)
return ( (lstrcmp(s,NANSTRING) -- 0) ? NANFLT . atof(s) );
/*en.codefloat: convert a double to a full float precision character string */
char *encodefloat(double x) {
return(encodenumber(x,FLOATPRECISIONFORMAT));
/*encodeshortfloat: convert double to less than full float precision string*/
char *encodeshortfloat(double x) {
return(encodenumber(x,SHORTFLOATPRECISIONFORMAT));
/*encodeint: convert an integer to a character string */
char *encodeint(int i) {
static char encodedint[MAXSTRING+1];
if (NANINT =- i)
lstrcpy(encodedint,NANSTRLNG);
else
sprintf(encodedint, "%i", i);
return(encodedint);
/*decodeint: convert a character string to an integer */
int decodeint(char *s) {
return( (lstrcmp(s, NANSTRING) -- 0) ? NANINT . atoi(s) );
char' *rangeerror(M4SPARAMID pid)
static char rangeerrortext[MAXSTRING+1];
M4SPARAM *param = pid2param(pid);
sprintf(rangeerrortext,
"%s must obey: %s%s", param->name,
encodefloat(param->low), (param->low -- NANFLT) ? "" . " .:,_ ");
sprintf(rangeerrortext + lstrlen(rangeerrortext),
"%s%s%s -- entry cleared.", param->name,
(param->high =- NANFLT) ? "" . " <= ", encodefloat(param->high));
ret.urn(rangeerrortext);

CA 02415685 2003-02-04
;* ~_imeinhours: returns time in hours since Jan l, 1970 */
dots ~ timeinhours (void) (
return ( ((double) time~NULL))/SECONDSPERHOUR );
/* m4stimeinhours: returns the time seen (simulated or real) b~; engine */
double m4stimeinhours(M4SENGINEID eid) {
return (!m4e[eid].simulatessp ?
timeinhoursC) . (m4e[eid].starttime +
(SIMHOURSPERREALSECOND*SECONDSPERHOUR*(timeinhours()-
m4e[eic::~].starttime))));
/* m4sdatetime: given hours since Jan 1, 1970, returns datetime:: string */
chair *m4sdatetime(double m4stime)
static char datetime[MAXSTRING+1];
tame t t = (time t) (m4stime * SECONDSPERHOUR); /* convert tc~ C time format*/
strftime(datetime,MAXSTRING,"%d-%b-%Y %X", localtime(&t));
return(datetime);
/* begin: code to perform conversions from macc4.c module's in!.ernal
moles/hr sodium leak units to the feedwater (lbs/hr) plus f~:edwater Na (ppb)
~ralues that macc4ssp presents to the user *,i
#de:Fine MW NA 22.99 /* molecular weight of Na */
double lb2kg(double 1b) ~ return((NANFLT == 1b) ? NANFLT : (1b~0.45359));
double kg2lb(double kg) return((NANFLT = kg) ? NANFLT (kg'0.45359));
double ppb2molesperkg(double ppb, double mw) {
re turn ((ppb =- NANFLT) ? NANFLT . (ppb*l.Oe-6/mw));
double molesperkg2ppb(double mpk, double mw) {
return ((mpk =- NANFLT) ? NANFLT . (mpk*l.Oe+6*mw));
/* nanmult: NaN aware multiplir_ation ~/
double nanmult(double x1, double x2)
return ((xl==NANFLT (~ x2==NANFLT) ? NANFLT . (xl*x2));
%* nandiv: NaN aware division */
double nandiv(double x1, double x2)
return ((xl==NANFLT ~I x2==NANFLT f~ x2==0.0) ? NANFLT . (xl~x2));
%* naleak2fwna: converts the Na leak units (moles/hr) used by nacc4.c
to feedwater Na (ppb) used by macc4ssp.c's user interface.
feed water flow in lbs/hr.*,/
dou:~le naleak2fwna(double naleak, double fwflow) {
return(molesperkg2ppb(nandiv(naleak,lb2kg(fwflow)), MW-NA)I;
/* fwna2naleak: inverse of naleak2fwna Csee above) */
double fwna2naleak(double fwna, double fwflow)
return(nanmult(ppb2molesperkg(fwna, MW NA), lb2kg(fwflow)));
/*end: code to perfarm unit conversions */
/*fixnanint: defaulting for undefined ints */

CA 02415685 2003-02-04
int fixnanint(int i, int fornan) {
rE:turn ( i==NANINT '.' fornan .. i ) ;
douk.-_.p~ fixnandbl (double x, double fornan) {
reaurn (x==NANFLT '.' fornan . x) ;
/* m4spath: MACC4SSP path is r_he directory in which the EXE is located */
/* the trailing \ is included in this path ( e.g. C:\MACC4SSP\ ) */
char *m4spath(void)
static char path[2*MAXSTRING+1] ;
int len =
GetModuleFileNarne(GetModuleHandle(M4SMODULENAME), path, 2*MAXS''RING);
for (len--; len >= 0 && path[len]!='\\'; len--) /* zap name.ext part*/
path [len] _' \0' ;
reaurn (path) ;
/* carl2s: returns t:he string associated with a control (or wir..dow) */
char *ctrl2s(HWND hCa=rl) {
static char s [MAXS'CRING+1] ;
GeaWindowText (hCtr:l, s, MAXSTRING) ;
reaurn ( s ) ;
/* ~;2ctrl: copies teat to the given control */
void s2ctrl(HWND hCt:rl, char *s) {
SetWindowText (hCtr:L, s) ;
static char marquebu:E[2*MAXSTRING+1]; /* stores scrolling marque */
/* c:learmarque: clears. the marque */
void clearmarque (void) {
l:>trcpy(marquebuf,,"");
/* marque: uses message line as a scrolling marque */
void marque (char *s;l
static BOOL firsttime = TRUE;
i.f: (firsttime)
clearmarque();
firsttime = FALSE;
lstrcat(marquebuf, s); /* add new string to end of old one */
ii. (lstrlen(marquebuf) > MAXSTRING) /*destroy older part of rnarque*/
lstrcpy(marquebuf, marquebuf+lstrlen(marquebuf)-MAXSTRING);
s2ctrl(GetDlgItem(hmainwin, IDC MESSAGELINE),marquebuf);
UpdateWindow(GetDlgItem(hmainwin, IDC MESSAGELINE));
/* nameprefix: returns the macc:4 prefix associated with a given engineid */
chair *nameprefix(M4SENGINEID eid) {
sciatic char prefix [MAXSTRING+:L] ;
sprintf(prefix, "M%i", eid);
r~=_turn (prefix) ;

CA 02415685 2003-02-04
/* ~nifile: returns the name of the engine's initialization file */
cha .vinifile(M4SENGINEID eid)
return(lstrcat(lstrcat(m4spat:h(),nameprefix(eid)),".INI"));
/*m4spointname: SSP point name for engineid/paramid pair */
char *m4spointname(:M4SENGINEID eid, M4SPARAMID pid)
static char pointname[MAXSTRING+1];
lstrcpy(pointname, nameprefix(eid));
return(lstrcat(pointname,pid2name(pid)));
/* updatechecksum: simple has: to enforce I/O consistency*/
void updatechecksum(M4SENGINEID eid, M4SPAR.AMID pid, char *s) ;,
if (pid != IDC CHECKSUM)
for (; *s; s++) if (isdigit (*s) ) m4e [eid] .checksum ~_ (*s -- ' 0' + 1) ;
/* .ini2s: reads a string from an initialization file. */
char *ini2s(M4SENGINEID eid, M4SPARAMID pid)
static char s [MAXSTRING+1] ;
GestPrivateProfile;String( M4SMODULENAME, pid2name(pid),
NANSTRING, s, MAXSTRING, inifile(eid));
updatechecksum(eid, pid, s);
rE~turn ( s ) ;
/* s2ini: writes a string to an initialization file */
void s2ini(M4SENGINEID eid, M4SPARAMID pid, char *s)
ij°
(!WritePrivate:ProfileString(M4SMODULENAME,pid2name(pid),s;inifile(eid)))
M M4SERROR("Error writing to initialization file.");
updatechecksum(eid, pid, s);
/* ctrl2ini: writes contents of a control to associated INI file keyword */
void ctrl2ini(M4SENGINEID eid, M4SPARAMID pid, HWND hCtrl) {
s'.?ini (eid, pid, c~trl2s (hCtrl) ) ;
/* ini2ctrl: reads .associated ini. file keyword into a control v/
void ini2ctrl(HWND hCtrl, int eid, int pid) {
s2ctrl(hCtrl, ini2s(eid,pid));
void dbl2ini(M4SENGINEID eid, M4SPARAMID pid, double x)
s2ini(eid,pid,encodedouble(x));
/* :ini2dbl: reads associated ini file keyword into a memory */
double ini2dbl(M4SENGINEID eid,, M4SPARAMID pid)
return(decodedouble(ini2s(eid,pid)));
/* int2ini: writes contents of field to associated ini file kevyword */

CA 02415685 2003-02-04
void int2ini(M4SENGINEID eid, M4SPARAMID pid, int i) {
:~2ini (eid, pid, encodeint (:i) ) ;
/* ini2int: reads associated in.i file keyword into a memory */
int ini2int(M4SENGIN13ID eid, M4SPARAMID pid)
return(decodeint(ini2s(eid,pid)));
/*pointnames: return:a name of points string */
char *pointnames(M4SH:.NGINEID eid) (
const char *pointsprefix = "Points: ";
static char pointnamebuf [2*MAXSTRING+1] ;
if: (m4e [eid] .local input)
sprintf(pointnamebuf,
"%sm%i { (afeed,bfeed,flag7,flagl2)", pointsprefix, eid);
else
sprintf (pointnawebuf ,
"%sm%i +
(afeed,bfeed,ph,~o4,%sbdtemp,%supdate,undo,reinit,flag~l,...,flag12)",
pointsprefix, eid, rn4e[eid].nh3enabled ? "nh3," : "",
rn4e [eid] . local input ? "" . "bd, fwna, ratio, ") ;
return (pointnamebui: ) ;
*geacheckboxstate: '."RUE if checkbox checked, else FLASE */
BOOL~ ge~tcheckboxstat;e: (HWND hcheckboxctrl) (
re:turn(SendMessage:(hcheckboxctrl, BM_GETCHECK, 0, 0) ? TRUE . FALSE);
%* setcheckboxstate: checks or unchecks a checkbox */
void. setcheckboxstate:(HWND hcheckboxctrl, BOOL checked)
Se:ndMessage(hcheckboxctrl, BM SETCHECK, checked, 0);
/* ini2dlg: loads al:. fields from the ini file into dialog box (main window)
*/
/*
checkboxes, which indicated controller modes, are loaded into memory
a.s well as into the form--this makes modes easily available (without
disk access) later on.
*/
void ini2dlg(HWND hDlg, int eid)
M4SPARAM *currentct:rl = pid2param(IDC FIRSTCONTROL);
M4SPARAM *lastctrl - pid2param(IDC_LASTCONTROL);
#define M INI2CHECKBOX(wheretostore,pid,default) \
wheretostore = fixnanint(ini2int(eid,pid),default), \
se~tcheckboxstate (GEstDlgItem (h.Dlg, pid) , wheretostore)
M INI2CHECKBOX(m4e[eid].logtofile, IDC LOGTOFILE, TRUE);
M~_INI2CHECKBOX(m4e[eid].simulatessp, IDC_SIMULATESSP, FALSE);
M_INI2CHECKBOX(m4e[eid].localinput, IDC LOCALINPUT, FALSE);
M INI2CHECKBOX(m4e[eid].nh3enabled, IDC NH3ENABLED, FALSE);
/*make point names on form match mode of-engine just loaded into form:*/
s2ctrl(GetDlgItem(hDlg, IDC~POINTNAMES), pointnames(eid));
for ( ; currentctr:l <= lastctrl; currentctrl++) /*load form's edit controls*/
if (STOREDEDIT CONTROL == currentctrl->type) /* with this engine's info */
ini2ctrl(GetDlgItem(hDl.g, currentctrl->pid), eid, currentctrl->pid);

CA 02415685 2003-02-04
/*
c:onstraininputs: ~~onstrains inputs to the control based on both
rv=°°~ kinds of user:a who are allowed to access it as well as
the
editions under which access to that control is allowed. This
routine either enables or disables each control accordingly.
*/
void constraininputs(HWND hDlg, M4SENGINEID eid, BOOL windowlocked)
M~6SpAR.AM *currentctrl = pid2param ( IDC FIRSTCONTROL) ;
M~6SPARAM *lastctr:L - pid2param(IDC LASTCONTROL);
BOOL localinput - m4e[eid].localinput;
BOOL running m4e zeid] . running;
BOOL nh3enabled - m4e~~eid].nh3enabled;
BOOL userisamanage:r - !wind.owlocked;
for ( ; currentctr_:L <= lastctrl; currentctrl++)
BOOL manageronlyctrl - MANAGERONLY LEVEL& currentctrl-,~leve:L;


BOOL operatoron:Lyctrl= ~FERATORONLY LEVEL& currentctrl->level;


BOOL staticctr:L STATIC ACCESS & currentctrl->access;


BOOL dynamicctr_L , & currentctrl-~~access;
- DYNAMIC ACCESS


BOOL localctrl - LOCAL ACCESS & currentctrl-~~access;


BOOL nh3ctrl - NFi;~ ACCESS ~ currentctrl-.access;


BOOL enablectr:l = TRUE; /* enabled until proven otherwise */
/* Decide if control is enabled by a process of elimination: consider,
in turn, each co,:ldition which could require that the control be disabled*/
if (manageronly~~trl && !userisamanager)
enablectrl = FALSE;
else if (operat~~ronlyctrl && userisamanager) /* password prompt */
enablectrl = FALSE; /*(managers have already entered the password)*/
else if (nh3ctrl && !nh3enabled)
enablectrl = FALSE;
else if (static~trl && running)
enablectrl = FALSE;
else if (dynam:icctrl && !running)
enablectrl = FALSE;
else if (localctrl && running s~& !localinput)
enablectrl = FALSE;
else if (localctrl && !runni.ng ~& !userisamanager)
enablectrl = FALSE;
/* else, since theta are no eb:jections from the authorities, control
enabled*/
EnableWindow(GetDlgItem(hDlg, currentctrl->pid), enablectrl);
/* :ini2engine: loads all parameters from i.ni file into memory */
/* .returns badchecksum: TRUE implies a bad check sum/corrupted file*/
BOOL ini2engine(M4SENGINEID eid) O
int oldchecksum = ini2int(eid, IDC CHECKSUM);
m~4e [eid] .blr.po4 = ini2dbl r: eid, IDC Po4 ) ;
m~4e [eid] . blr . ph = ini2dbl ( e:id, IDC_PH ) ; .
m~4e [eid] .blr.bdtem.p = ini2db.L ( eid, IDC BDTEMP ) ;
m4e[eid].blr.nh3 = ini2dbl~; r=_id, IDC_NH~ );
m4e[eid].lasteventtime = ini2int ( eid, IDC LASTEVENTTIME);
m4e [eid] . eventid - ini2int ( eid, IDC--EVENT~D) ;
m4e[eid].checksum. = 0; /* we don't include first few params in checksum */
/* cause they can legitimately change */

CA 02415685 2003-02-04
m~4e [eid] . starttime = ini2dbl 1' eid, IDC STARTTIME) ;
m~fe [eid] . running - fixnanint (ini2int ( eid, IDC RUD1NING) , FALSE) ;
m "y'[eid].logtofile = fixnanint(ini2int ( eid, IDC LOGTOFILE), TRUE)
m ~eid] . blr. t = :i:ni2dbl ( eid, IDC_T ) ;
m4e[eid].blr.famin = ini2dbl( eid, IDC FAMIN );
m4e[eid].blr.famax = ini2dbl( eid, IDC-FAMAX );
m4e(eid].blr.fadef = ini2dbl( eid, IDC~FADEF );
m4e[eid].blr.po4a = ini2dbl( eid, IDC P04A );
m4e[eid].blr.ratioa = ini2dbl( eid, IDC RATIOA );
m4e[eid].blr.spga = ini2dbl( eid, IDC SPGA );
m4e[eid].blr.fbmin = ini2dbl( eid, IDC_FBMIN );
m4e [eid] .blr. fbma;!c = ini2dbl ( eid, IDC FBMAX ) ;
m4e [eid] .blr. fbde:E = ini2dbl ( eid, IDC-FBDEF ) ;
m4e[eid].blr.po4b = ini2dbl( eid, IDC P04B );
m4e [eid] .blr.ratio:o = ini2d:bi. f eid, IDC RATIOe ) ;
m4e[eid].blr.spgb = ini2dbl( aid, IDC SPGB );
m4e [eid] .blr.m = :i:ni2dbl ( eid, IDC M T~;
m4e[eid].blr.dpo4 - ini2dbl( e.id, IDC DP04 );
m4e [eid] .blr.po4setpoint = ir~i.~dbl ( eid, IDC P04SETPOINT ) ;
m4e[eid].blr.minpo4 = ini2dbl.( eid, IDC MINP04 );
m4e [eid] .blr.maxpo4 = ini2dbl. ( eid, IDC' MAXP04 ) ;
m4e [eid] . blr. dph := ini2dbl ( eid, IDC DPH ) ;
m4e [eid] . blr . phsetpoint = i:ni.~dbl ( eid, IDC PHSETPOINT ) ;
m4e[eid].blr.dphsetpoint = ini2dbl( eid, IDC DPHSETPOINT );
m4e [eid] .blr.napo4ratio = i:ni.a?dbl ( eid, IDC NAP04RATI0 ) ;
m4e[eid].blr.minna.po4ratio = ini2dbl( eid, IDC MINNAP04RATI0 );
m~6e [eid] .blr.maxn<~yo4ratio = ini2dbl ( eid, IDC' MAXNAP04RATI0 ) ;
m~6e [eid] . blr. max :a.~mple interval = ini2dbl ( ei3, IDC MAX SAMF?LE
INTERVAL ) ;
m4e [eid] .blr.maX owtofbox adjustment =
ini2dbl ( eid, I17~S MAX OL1TOFBOX ADJUSTMENT ) ;
m~6e (eid] .blr.max :r.=_Iative error = ini2dbl ( eid, IDC MAX RELATIVE ERROR
) ;
m~6e (eid] .blr.beps = ini2db1 ( eid, IDC BEPS ) ;
m~6e [eid] . blr . bdmin = ini2dbl ( eid, IDC BDMIN ) ;
m4e [eid] . blr. bdmax = ini2dbl ( eid, IDC' B'DMAX ) ;
m~6e [eid] . fwflow = ini2dbl ( ei.d, IDC FWFLOW) ;
m~6e [eid] . blr. naleakmin = fwna2nalea>-cc ( ini2dbl ( eid, IDC MiNFWNA ) ,
m4e (eid] . fwflow) ;~
m4e[eid].blr.naleakmax = fwna~naleak(ini2dbl( eid, IDC_MAXFWNA ),
m4e [eid] . fwflow) ;
m~6e [eid] .blr.l = fwna2naleak (ini2dbl ( eid, IDC FWNA) ,
m4e [eid] .~wflow) ;
mile [eid] .blr. fdt :- ini2dbl ( eid, IDC FDT ) ;
m4e [eid] .blr.last't = ini2dbl ( eid, IDC LASTT ) ;
m4e[eid].blr.lastbdtemp = ini2dbl( eid, IDC LASTBDTEMP );
m4e [eid] .blr. lastpo4 = ini2dbl ( eid, IDC ~:~ASTP04 ) ;
m4e [eid] .blr.lastnh3 = ini2dbl ( eid, IDC' LASTNH3 ) ;
m4e [eid] .blr.lastph = ini2dbl ( eid, IDC LASTPH ) ;
m4e[eid].blr.b4lastt = ini2dbl( eid, IDC'. B4LASTT );
m4e[eid].blr.b4lastbdtemp = ini2dbl( eid, IDC B4LASTBDTEMP );
m4e [eid] .blr.b4lastpo4 = ir..i:?dbl ( eid, I:DC B4LASTP04 ) ;
m4e [eid] .blr.b4lastnh3 = ini:?dbl ( eid, IDC B4LASTNH3 ) ;
m4e[eid].blr.b4lastph = ini2dbl( eid, IDC B4LASTPH );
m4e [eid] . blr . bd = ini2dbl ( ei<i, IDC BD ) ;
m4e[eid].blr.lastl = ini2dbl( eid, IDC LASTL );
m4e [eid] .blr.last:bd = ini2dbl ( eid, IDC LASTBD ) ;
m4e [eid] .blr.dt [0] - ini2dbl ( eid, IDC I~T1 ) ;
m4e [eid] .blr.dt [1] - ini2dbl ( eid, IDC' UT2 ) ;
m4e [eid] .blr. fb [0] - ini~dbl ( eid, IDC: FB1 ) ;
m4e [eid] . blr. fb (1] - ini2dbl ( eid, IDC' FB2 ) ;
m4e [eid] .blr. fb [2] - ini2dbl ( eid, IDCiFB3 ) ;
m4e [eid] .blr. fa [0] - ini2d.fa1 ( eid, IDC' FAl ) ;
m4e [eid] . blr. fa [1] - ini2dbl ( eid, IDC_FA2 ) ;
m.4e [eid] .blr. fa [2] - ini2dbl ( eid, IDC_FA3 ) ;
m4e[eid].blr.lastdt[0] - ini:2db1( eid, IDC LASTDT1 );
m4e [eid] .blr.lastdt (1] - ir~i:zdbl ( eid, ~:DC LASTDT2 ) ;

CA 02415685 2003-02-04
m4e [eid] .blr.lastfa [0] - ini2dbl ( eid, IDC LASTFA1
mv~e [eid] .blr. lastfa [1] - ini2dbl ( eid, IDC-LASTFA2 ) ;
muse (eid] .blr.lastfa [2] - ini2dbl ( eid, IDC-LASTFA3 ) ;
m~'.~~ [eid] .blr.lastfb[0] - ini2dbl ( eid, IDC-LASTFB1 ) ;
m eid].blr.lastf:b[1] - ini2dbl( eid, IDC LASTFB2 );
m4e[eid].blr.last.E:b[2] - ini2dbl( eid, IDC LASTFB3 );
m4e [eid] . blr . updatestatus =
fixnanint(ini2.int( eid,IDC: UPDATESTATUS),E UPDATEOK);
m4e [eid] .blr.lastupdatestatus~-~=
fixnanint(ini2:i:nt( eid, IDC LASTUPDATESTATUS ), E_UPDATEOF~;);
m4e[eid].blr.initstatus =fixnanint(ini2int( eid, IDC INITSTATUS),!E INITOK);
m4e[eid].blr.undoa:ble = fixnanint(ini2int( eid, IDC UNDOABLE), FALSE);
m4e[eid].simulateasp = fixnani.nt(ini2int ( eid, IDC SIMULATE:ySP),:»ALSE);
m4e[eid].localinput = fixnani.nt(ini2int ( eid, IDC LOCALINPUT),FALSE);
m4e [eid] .nh3enabl~=_~~ = fixnanint (ini2int ( eid, IDC~NH3ENABLEL~) , FALSE)
;
return (m4e [eid] . badchecksum =
(m4e[eid].running &.~ /* ignore if engine nc>t running or */
oldcheckaum != IGNOF:ECHECKSUM && /* if ignore checksum is flagged */
oldchecka~am ! = m4e [e~id] . checksum) ) ;
/* engine2ini: writers in memory engine representation to INI file */
void engine2ini(M4SE1VGINEID eid)
dY~l2ini ( eid, IDC :P04, m4e [ei.d] .blr.po4 ) ;
dbl2ini ( eid, IDC-_:PH, m4e (eid] .blr.ph ) ;
dbl2ini ( eid, IDC _13DTEMP, m4e [eid] . blr . bdtemp ) ;
dbl2ini ( eid, IDC 1VH3, m4e (ei.d] .blr.nh3 ) ;
int2ini ( eid, IDC_LASTEVENTTIME, m4e (eid] .lasteventtime ) ;
int2ini ( eid, IDC EVENTID, as4e [eid] . event id ) ;
m9:e[eid].checksum = 0; /* don't include above params in checksum*/
/* cause they can legitimately change */
dbl2ini ( eid, IDC_STARTTIME, m4e [eid] . starttime) ;
int2ini ( eid, IDr_RUNNING, m4e [eid] . running ) ;
int2ini ( eid, IDC~LOGTOFILE, m4e[eid].logtofile );
dbl2ini ( eid, IDC _'r, m4e [eid] . blr. t ) ;
dbl2ini ( eid, IDC_:~AMIN, m4e (.eid] .blr. famin ) ;
dbl2ini ( eid, IDC_:»AMAX, m4e [,ei.d] .blr. famax ) ;
dbl2ini ( eid, IDC_:~ADEF, m4e [;eid] .blr. fadef ) ; .
dY~l2ini ( eid, IDC _P04A, m4e [e~id] . blr . po4a ) ;
dbl2ini ( eid, IDC_'.~ATIOA, m4e [eid] .blr.ratioa ) ;
dbl2ini ( eid, IDC_,3PGA, m4e [eid] .blr. spga ) ;
dbl2ini ( eid, IDC_:FBMIN, m4e f.eid] .blr. fbmin ) ;
dbl2ini ( eid, IDC_:FBMAX, m4e [eid] .blr. fbmax ) ;
dbl2ini ( eid, IDC_FBDEF, m4e (:ei.d] .blr. fbdef ) ;
dbl2ini ( eid, IDC_P04B, m4e [eid] .blr.po4b ) ;
dbl2ini ( eid, IDC_:f2ATIOB, m4e I.eid] .blr. ratiob ) ;
dbl2ini ( eid, IDC ,SPGB, m4e Ceid] . blr. spgb ) ;
dbl2ini ( eid, IDC-_1H, m4e [eid] .blr.m ) ;
dbl2ini ( eid, IDC_DP04, m4e [e~id] .blr.dpo4 ) ;
dbl2ini ( eid, IDC_P04SETPOIN'f, m4e [eid] .blr.po4setpoint ) ;
dbl2ini( eid, IDC MINP04, m4e~[eid].blr.minpo4 );
dbl2ini ( eid, IDC~_'~lAXP04, m4e: [eid] .blr.maxpo4 ) ;
dbl2ini ( eid, IDC_DPH, m4e [ei.d] .blr.dph ) ;
dY~l2ini ( eid, IDC_PHSETPOINT, m4e [eid] .blr.phsetpoint ) ;
dbl2ini( eid, IDC_DPHSETPOINT, m4e[eidJ.blr.dphsetpoint );
dbl2ini( eid, IDC_:~1AP04RATI0, m4e(eid].blr.napo4ratio );
dbl2ini ( eid, IDC_~IINNAP04RA7.'I0, m4e Ceid] .blr.minnapo4ratio
dbl2ini( eid, IDC_~IAXNAP04RATI0, m4e[eid].blr.maxnapo4ratio l;
dbl2ini ( eid, IDC_;~IAX SAMPLE INTERVAL, m4e [ei.d] .blr.max_sampie_interval
) ;
dbl2ini ( eid, IDC :~IAX OUTOFB2)X ADJUSTMENT,

CA 02415685 2003-02-04
m4e~E~id] .blr.max outofbox adjustment ) ;
dbl2ini( eid, IDC MAX RELATIVE ERROR, m4e[eid].blr.max relative error );
dbl2 ini ( eid, IDC_ F3EPS . m4e [ei3] . blr . beps ) ;
dr'"'~ini ( eid, IDC F3DMI1V, m4e [eid] .blr.bdmin ) ;
dY .ini ( eid, IDC'_F3DMAX, m4e [eid] . blr . bdmax ) ;
dbl2ini ( eid, IDC L~WFLOW, m4e [eid] . fwflor~a ) ;
dbl2ini ( eid, IDC t~IINFWNA,
decodedouble(enGOdefloat(
naleak2fwna (m4e [eid] .b:lr.naleakmin, m4e [eid] . fwflow) ) ) ) ;
dbl2ini ( eid, IDC PrIAXFWNA,
decodedouble(encodefloat(
naleak2fwna(m4e[eid] .blr.naleakmax,m4e[eid] .fwflow) ) ) ) ;
dbl2ini( eid, IDC FWNA,
decodedouble (en~:odefloat (naleak2fwna (m4e [eid] . blr. 1, m4e [eid] .
fwflow) ) ) ) ;
dbl2ini ( eid, IDC 1?DT, m4e [eid] . blr. fdt ) ;
dbl2ini ( eid, IDC'_~..ASTT, m4e [eid] .blr.lastt ) ;
dbl2ini ( eid, IDC I:~ASTBDTEMP, m4e [eid] .blr.lastbdtemp ) ;
dbl2ini ( eid, IDC I:~ASTP04, m4e [eid] .blr. lastpo4 ) ;
dbl2ini ( eid, IDC _I:.ASTNH3, m4e [eid] .blr. lastnh3 ) ;
dbl2ini ( eid, IDC _','~ASTPH, m4e [eid] .blr.lastph ) ;
dbl2ini ( eid, IDC_134LASTT, °n4e [eid] .blr.b4lastt ) ;
dbl2ini ( eid, IDC 134LASTBDTEMP, m4e [eid] .blr.b4lastbdtemp ) ;
dbl2ini( eid, IDC'B4LASTP04, m4e[eid].blr.b4lastpo4 );
dbl2ini ( eid, IDC_134LASTNH3, m4e [eid] .blr.b4lastnh3 ) ;
dbl2ini ( eid, IDC _I34LASTPH, m4e [eid] . blr. b4lastph ) ;
dbl2ini ( eid, IDC_I3D, decoded.ouble (encodefloat (m4e [eid] .blr.bd) ) ) ;
dbl2ini ( eid, IDC :~ASTL, m4e [eid] .blr.lastl ) ;
dbl2ini ( eid, IDC ::~ASTBD, m4e [eid] .blr. lastbd ) ;
dbl2ini ( eid, IDC _DTl, m4e [eid] .blr.dt [0] ) ;
dbl2ini ( eid, IDC_ DT2, m4e [eid] .blr.dt [1] ) ;
dbl2ini ( eid, IDC'_:~B1, m4e [ei.d] .blr. fb [0] ) ;
dbl2ini ( eid, IDC :~B2, m4e [eid] .blr. fb [1] ) ;
dbl2ini ( eid, IDC~:~B3, m4e [eid] .blr.fb[2] ) ;
dbl2ini ( eid, IDC :~Al, m4e [ei,d] .blr, fa [0] ) ;
dbl2ini ( eid, IDC':rA2, m4e Cei,dl .blr. fa [1] ) ;
dbl2ini ( eid, IDC'_:FA3, m4e [ei.d.l .blr. fa [2] ) ;
dbl2ini ( eid, IDC LASTDT1, m9;e [eid] .blr. lastdt [0] ) ;
dbl2ini( eid, IDC':LASTDT2, m4e[eid].blr.lastdt[1] );
dbl2ini ( eid, IDC'_:LASTFAl, m4e [eid] . blr. lastfa [0] ) ;
dbl2ini ( eid, IDC_LASTFA2, m9;e [eid] .blr.lastfa [1] ) ;
dbl2ini ( eid, IDC LASTFA3, m9:e [eid] .blr. lastfa [2] ) ;
dbl2ini ( eid, IDC'LASTFB1, m9:e [eid] .blr. lastfb [0] ) ;
dbl2ini( eid, IDC LASTFB2, m4e[eid].blr.lastfb[1] );
dbl2ini ( eid, IDC_LASTFB3, m4e [eid] .blr.lastfb [2] ) ;
int2ini ( eid, IDC_UPDATESTA')?LTS , m4e [eid] .blr.updatestatus
int2ini ( eid, IDC_LASTUPDATESTATUS, m4e [eid] .blr.lastupdatestatus ) ;
int2ini ( eid, IDC_INITSTATUS, m4e [eid] .blr. initstatus ) ;
int2ini( eid, IDC UNDOABLE, m4e[eid].blr.undoable );
int2ini ( eid, IDC_SIMULATESSP, m4e[eid].simulatessp );
int2ini ( eid, ID~C_LOCALINPUTr m4e [eid] . local input ) ;
int2ini ( eid, IDC NH3ENABLEI), m4e [eid] .nh3enabled ) ;
int2ini( eid, IDC CHECKSUM, m4e[eid].checksum);
/* :randscierror: generates a random SCIL error code */
SCLError randscierror(double scilerrorfraction)
static SCIError scierr[]={
ERROR DATA TYPE MISMATCH,
ERROR INVALID PROJECT NAME,
ERROR LINK ALREADY ESTABLISHED,
ERROR LINK NOT CURRENTLY ESTABLISHED,
ERROR POINT' NAN-fE NOT FOU1~D,
ERROR PROJEC T N'O~' FOUND,

CA 02415685 2003-02-04
ERROR SMARTSCAN PLUS NOT RUNNING,
ERROR_SMARTCARD_TIMEOUT
i "( rand() <= RAND MAX*(1.()-scilerrorfraction))
turn (ERROR NONET;
else {
int nerrors = sizeof(scierr)/sizeof(scierr[0]);
return(scierr[rand() % nerrors]>;
/*simulateoperator: simulates input from the operator at regular intervals*/
void simulateoperator(M4SENGINEID eid) {
double t = m4stimeinhours (eicil ;
if (t - m4e [eid] .lasteventt.irne >= SIMHOURSPEREVENT) {
if (m4e [eid] .eventid <
sizeof (simsspinputs) /sizeof (simsspinputs [0] ) -1) {
m4e [eid] .event id++;
int2ini ( eid, IDC EVENT:CD, m4e [eid] . event id ) ;
/* just repeat last event. when we get to the end of the array*/
m4e [eid] . lasteventtime = t ,;
int2ini ( eid, IDC LASTEVEZdTTIME, m4eCeid].lasteventtime );
m4e [eid] . current event = sirnsspinputs [m4e [eid] . event id] ;
%* else robo-operator is idle */
/* 'The following sim* function:a emulate the corresponding SCIL routines:*/
void simSCInitCommLink(SCIError *err) {
*err = randscierror(SCILERRORFRACTION);
void simSCCutCommLink(SCIError *err) {
*.=_rr = randscierror(SCILERRORFRACTION);
void simSCReadDigitalPoint(const char *s,BOOL *bit,SCIError *err) {
M~4SENGINEID eid = s[1] - '0'; /* hack to pull off engine id */
/* from point name prefix */
s += lstrlen(nameprefix(eid)); /* skip over the engine specific prefix*/
if (lstrcmp (s, pid.2name (IDC ~;JPDATE) ) ==0)
*bit = m4e[eid].currentevent.update;
else if (lstrcmp(s~, pid2name(IDC UNDO))==0)
*bit = m4e [eid] . currenteve:nt . undo ;
else if (lstrcmp(e;, pid2name(IDC REINIT))==0)
*bit = m4e[eid].currenteve:nt.reinit;
else
M BUGALERT("Invaalid digital point during simulated SSP Read.");
*err = randscierror(SCILERRORFRACTION);
void simSCReadAnaloc_~Point(const char *s, float *value,SCIError *err)
M4SENGINEID eid = s[1] - '0'; /* hack to pull off engine id */
/* from point name prefix */
s += lstrlen(nameprefix(eid)); /* skip over the engine specific prefix*/
i f ( lstrcmp ( s , pi.d2name ( IDC_PH) ) ==0 )

CA 02415685 2003-02-04
*value = e_phmodel(&m4e[eid].blr);
e~:.se if (lstrcmp(s, pid2name(IDC P04))==0)
*value = e-po4model (&m4e [eid] .blr) ; -
e- ~ if (lstrcmp(s, pid2name(IDC NH3))==0)
alue = m4e[eidJ.blr.nh3;
e:_se if (lstrcmp(s, pid2name(IDC BDTEMP))==0)
*value = m4e[eid].blr.bdtemp; -
a:se
M BUGALERT("Inva.Lid analog point during simulated SSP Read.");
*err = randscierro:r (SCILERROF~.FRACTION) ;
void simSCWriteDigitalPoint(corast char *s,BOOL bit,SCIError *err)
M~ESENGINEID eid = a [1] - ' 0' ; ; * hack to pull off engine id */
/* from point name prefix */
i1. (eid =- current~~ngineid) {
char buf [MAXSTR:IIVG+1] ;
s += lstrlen(nam~~prefix(eid)); /* skip over the engine specific prefix*/
if (lstrcmp(s, pid2name(IDC UPDATE))==0) {
m4e (eidJ . curre:ntevent . update = bit ;
sprintf(buf, "'update=%i ", bit);
else if (lstrcmp(s, pid2name(IDC UNDO))==0)
m4e (eid] . curre:ntevent . undo = bit
sprintf (buf, "'vsndo=%i ", bit) ;
else if (lstrcmp(s, pid2name(IDC REINIT))==0)
m4e (eid] . curre:ntevent . rei.nit = bit ;
sprintf(buf, "reinit=%i '", bit);
}lse if (lstrcmp(s, pid2name(IDC FLAG1))==0)
sprintf(buf, "flags=(%i,"', bits;
else if (lstrcmp(s, pid2name(IDC-FLAG2))==0)
sprintf(buf, "%i,", bit);
else if (lstrcmp(s, pid2name(IDC-FLAG3))==0)
sprintf(buf, "%i,", bit>;
else if (lstrcmp(s, pid2name(IDC_FLAG4))==0)
sprintf(buf, "%i,", bit);
else if (lstrcmp(s, pid2name(IDC_FLAGS))==0)
sprintf(buf, "%i,", bit);
else if (lstrcmp(s, pid2name(IDC_FLAG6))==0)
sprintf(buf, "%i,", bit);
else if (lstrcmp(s, pid2name(IDC FLAG'7))==0)
sprintf (buf, m4e [eid] .loc:alinput ? "flags=(%i, " . "%i, ", bit) ;
else if (lstrcmp(s, pid2name(IDC_FLAGS))==0)
sprintf(buf, "%i,", bit);
else if (lstrcm;p(s, pid2name(IDC~FLAG9))==0)
sprintf(buf, "%i,", bit),;
else if ( lstrcm;p ( s, pid2r:~ame ( IDC,FLAG1.0 ) ) ==0 )
sprintf(buf, "%i,", bit.),;
else if (lstrcmp(s, pid2narne(IDC_FLAG11))==0)
sprintf(buf, "%i,", bit.);
else if (lstrcmp(s, pid2narne(IDC-FLAG12))==0)
sprintf (buf, "%i) ", bi.t) ;
else
sprintf(buf,"%s=%i ", s, bit);
marque(buf);
*err = randscierrcr(SCILERRORFRACTION);

CA 02415685 2003-02-04
void. simSCWriteAnalogPoint(canst char *s,f:loat f,SCIError *err
M"°~~NGINEID eid = s[1] - '0'; /* hack to pull off engine id */{
/* from paint name prefix */
if (currentengineid =- eid)
char buf [MAXSTRING+1] ;
s += lstrlen(nameprefix(eid)); /* skip over the engine specific prefix*/
if (lstrcmp(s, pid2name(IDC AFEED))==0)
sprintf(buf, "a=%.5~ ", 1:~;
else if (lstrcmp(s, pid2name(IDC BFEED))==0)
spr{ntf(buf, "b=%.5g ", i:);
else
#if TESTING
sprintf(buf,"%s=%.5g ", s, f);
#else
sprintf(buf,"");
#endif
marque(buf);
*err = randscierror(SCILERRORFRACTION);
/*isvalidnumber: if s is a valid number or numeric prefix, TRUE, else FALSE*/
BOO:L isvalidnumber(char *s) {
char *endofnumber = NULL;
double value = strtod(s, &endofnumber);
if (value <_ -FLT_MAX ~~ value >= FLT MAX)
return(FALSE); /* we require that number be in range of f:oats~ */
else /* check if entire number was read by strtod */
return(*endofnumber =- '\0' ? TRUE . FALSE);
/* isnumericprefix: FALSE if s is not valid prefix to a number, else TRUE */
BOO:L isnumericprefix:(char *s) {
s += strspn(s, " "); /* skip leading whitespace 'cause strtad() does*/
if (lstrcmp(s,".")==0
lstrcmp (s, "-") ==0 lst:rcmp (s, " . ") ==0 ( ~
lstrcmp (s, "+" ) ==0 lst:rcmp (s, "+. ") ==0)
return(TRUE); /* special cases of valid prefixes that are not numbers*/
else
return(isvalidnu.mber(s) ) ;
/* forcevalidnumber: truncates the string to make it a valid number. */
char *forcevalidnumt~er(char *s) {
if (!isvalidnumber(s))
lstrcpy(s, NANSTRING);
return(s);
%*inrange: is x between low and high (ignores NANFLT) */
BOOL inrange(double x, double low, double high) {
if (x =- NANFLT)
return(TRUE);
else if ((low != rTANFLT && x < low) ~~ (high != NANFLT && x > high))
return(FALSE);
else
return(TRUE);
/* forceinrange: farces x between low and high; ignores NANFLT */

CA 02415685 2003-02-04
double forceinrangeldouble x, double low, double high) {
if (x =- NANFLT)
rseturn (x) ;
e~~ if (low != NFSIFLT && x <= low)
_._turn(low);
else if (high != N~~.NFLT && x >= high)
return(high);
else
return(x);
/* updatessppoints: Nets flags that force MACC4SSP to update SSP
action flags (update, undo, and reinit) and status flags */
/*(a~ctual SSP settings are made in response to the WM TIMER message)*/
/* (need to do this because initial settings of SSP points are often
hard to predict in advance, at least in my experience) */
void updatessppoints(M4SENGINEID eid) {
m4e[eid].1_lagsuptodate = FALSE;/*clear old flags...*/
m4e[eid].actionsupt.odate=FALSE;/*...and old actions*/
/*
SmartCard Interface hibrary (SCIL) based routines for accessing SmartScan Plus
*/
SCIE;rror sciinit (M4SENGINEID eid) {
SC'IError err = ERROR_NONE;
#if WORKAROUND
static BOOL initia:Lized = FALSE;
if (initialized) {
m4e [eid] . scierr :- err;
return(err);
#endi f
if' ( !m4e [eid] . simu:Latessp) SCInitCommLink (&err) ;
if (ERROR SMARTSCAif PLUS_NOT RUNNING =- err)
updatessppoints(eid); /* when SSP does start up, points must be*/
/* in a well defined initial state */
m4:e[eid].scierr = f~rr; /* note that this sets the scierr field */
/* r_o ERROR_NONE if Init succeeded *;"
#if WORKAROUND
if: (err =- ERROR_NONE) initialized = TRUE;
#endif
return (err) ;
SCIE:rror scifini (M4S:ENGINEID ei.d) {
SC:IError err=ERROR_NONE;
#if WORKAROUND
return (err) ;
#endif
if. ( !m4e [eid] . simulatessp) SC:CutCommLink (&err) ;
return(err);
/* rnaybestoreerror: remember error if the 1st seen since errors were cleared
*/
SCIError maybestoreerror(M4SENGINEID eid, M4SPARAMID pig, SCIE:rror err)
i1° (m4e [eid] . scie:rr =- ERROR_ NONE && err ! = ERROR NONE)
m4e [eid] . scierr = err;
m4e [eid] . pidscierr = pig;
rf~turn (err) ;
%* v~imedoutbefore: returns trues if the system timed since errors Were
cleared*/


05/07i2405 12:01 FAg 615 787 3558 _~ BLG CANADA ~ 002
11 P P E ~1 D I I 8
53
"..~,..,._r. _,.,."_.
...._....."".~,"~.".~.~.....w....,.~,.,..~,.y".,~.,.,..,...» , . ,~,.,
~.w.,....".~ .... "",".,o,~rM»",,","," a.,.,............,-... ......"r",
,..~.....~,..~.__,., "_~
CA 02415685 2003-02-04


05/07/2003 12:01 FAg 813 787 3558 .~_,BLG CANADA ~ 003
,._. - ...
a ~ ~ H 0 w
~~~.51~'~ ~~~ ~ ~ ~~ ~ a ~ a ~~ ' !5 .~~~ ~ LJLJ Q
.... _...""___...~.~a.".,. . . ...
.., ~..,.~.","".,.". - . ~ . . _.,... _....... ..._.. .
CA 02415685 2003-02-04


05/07/2003 12:01 FAX 813- 787 3558 BLG CA1~ADA ~J004
LJ o
_. w
W


_ o
a


s


a 0 g v o
~ ' ~.
~


~ ~ .~,~
a


r.a U
'-'~'


al


x a x



u1
~ a ~ ~~ ~a ~ Oa



CA 02415685 2003-02-04




Image

05/0,7/2003.12:02 FA8 613 787 3558 BLG CANADA . ~ 006
a
w
n fn
,.
v
an o
d
a v
rn
v
0
H
d
a
. a
d
c
a
E
E
0
U
n U7



a
~


a .~.
a a-
io



a a~
~


_



m w


~


U O
v


m L


0


ou


U
D
.D
N
N
f..~:
Z
d-
CL
_._.. __~"."~",~,~...
CA 02415685 2003-02-04 '""°""'~"""""'°"'"""*""'",. ~'~,°-
'°~-~ .~ , . ,.... . _......_._. .

05/07/Z009 12:02 FAX 61~ 787 3558 BLG C~~~A
8
tl~
g n
Q_
oa
0 '"
o a~
R op
._. __ r, t ~~ .. ~Q~ ~ to
m
0
V
V



a a


~ ~ Ln


p p ~ .v
~


a ~ ~ ~ a
3~
~


g ~ ~
a D
"


Q ;t~ ~ ~ ~ _.s-~ N
c U


00 ~ ~=~ as ~ '~~ a ~ ~ a cn Q
m


a, w



2


.. ~ as o


0 s U


e..,~ na Qa
Z~~~ a


U
O
U
Q?
I
d-
CA 02415685 2003-02-04 ., ... . ."," ~ w""",~ .... . . .

Representative Drawing
A single figure which represents the drawing illustrating the invention.
Administrative Status

For a clearer understanding of the status of the application/patent presented on this page, the site Disclaimer , as well as the definitions for Patent , Administrative Status , Maintenance Fee  and Payment History  should be consulted.

Administrative Status

Title Date
Forecasted Issue Date 2003-12-23
(22) Filed 1995-08-24
(41) Open to Public Inspection 1996-04-12
Examination Requested 2003-02-04
(45) Issued 2003-12-23
Expired 2015-08-24

Abandonment History

There is no abandonment history.

Payment History

Fee Type Anniversary Year Due Date Amount Paid Paid Date
Request for Examination $400.00 2003-02-04
Registration of a document - section 124 $50.00 2003-02-04
Registration of a document - section 124 $50.00 2003-02-04
Application Fee $300.00 2003-02-04
Maintenance Fee - Application - New Act 2 1997-08-25 $100.00 2003-02-04
Maintenance Fee - Application - New Act 3 1998-08-24 $100.00 2003-02-04
Maintenance Fee - Application - New Act 4 1999-08-24 $100.00 2003-02-04
Maintenance Fee - Application - New Act 5 2000-08-24 $150.00 2003-02-04
Maintenance Fee - Application - New Act 6 2001-08-24 $150.00 2003-02-04
Maintenance Fee - Application - New Act 7 2002-08-26 $150.00 2003-02-04
Maintenance Fee - Application - New Act 8 2003-08-25 $150.00 2003-08-01
Final Fee $532.00 2003-10-02
Maintenance Fee - Patent - New Act 9 2004-08-24 $200.00 2004-08-03
Maintenance Fee - Patent - New Act 10 2005-08-24 $250.00 2005-08-03
Maintenance Fee - Patent - New Act 11 2006-08-24 $250.00 2006-07-31
Maintenance Fee - Patent - New Act 12 2007-08-24 $250.00 2007-07-30
Maintenance Fee - Patent - New Act 13 2008-08-25 $250.00 2008-07-31
Maintenance Fee - Patent - New Act 14 2009-08-24 $250.00 2009-08-04
Maintenance Fee - Patent - New Act 15 2010-08-24 $450.00 2010-07-30
Maintenance Fee - Patent - New Act 16 2011-08-24 $450.00 2011-08-01
Maintenance Fee - Patent - New Act 17 2012-08-24 $450.00 2012-07-30
Maintenance Fee - Patent - New Act 18 2013-08-26 $450.00 2013-07-30
Maintenance Fee - Patent - New Act 19 2014-08-25 $450.00 2014-08-18
Owners on Record

Note: Records showing the ownership history in alphabetical order.

Current Owners on Record
BETZDEARBORN INC.
Past Owners on Record
BETZ LABORATORIES, INC.
BOYETTE, SCOTT M.
BURGMAYER, PAUL R.
GUNTHER, JOHN C.
THUNGSTROM, ERIC A.
WORRELL, NORMAN B.
Past Owners that do not appear in the "Owners on Record" listing will appear in other documentation within the application.
Documents

To view selected files, please enter reCAPTCHA code :



To view images, click a link in the Document Description column. To download the documents, select one or more checkboxes in the first column and then click the "Download Selected in PDF format (Zip Archive)" or the "Download Selected as Single PDF" button.

List of published and non-published patent-specific documents on the CPD .

If you have any difficulty accessing content, you can call the Client Service Centre at 1-866-997-1936 or send them an e-mail at CIPO Client Service Centre.


Document
Description 
Date
(yyyy-mm-dd) 
Number of pages   Size of Image (KB) 
Abstract 2003-02-04 1 36
Claims 2003-02-04 21 903
Drawings 2003-02-04 10 199
Representative Drawing 2003-03-20 1 16
Cover Page 2003-03-20 2 63
Description 2003-02-04 127 5,936
Cover Page 2003-11-19 2 63
Correspondence 2003-02-13 1 43
Assignment 2003-02-04 4 140
Correspondence 2003-03-20 1 14
Correspondence 2003-04-15 1 60
Correspondence 2003-10-02 1 26