Spreadsheet Problem Solving for ChE Faculty
User- defined Functions
One of the more useful extensions of Excel via VBA is the creation of user-defined
functions (UDF’s). User-defined functions play a similar role to built-in functions,
such as SQRT, PI, and VLOOKUP. Functions are used in Excel formulas or VBA
expressions, and they return values in the place where the function name appears.
Examples: computing the area of a cone, base + lateral
Built-in Excel functions: =PI()*Rad^2+PI()*Rad*SQRT(Rad^2+Ht^2)
Built-in VBA functions:
here]
Area=pi*r^2+pi*r*Sqr(r^2+h^2)
[note: pi is a variable
User-defined function:
=ConeArea(Rad,Ht)
[on the spreadsheet]
Area=ConeArea(r,h)
[in VBA code]
User-defined functions present several advantages: “packaging” a calculation in a
function
¾ standardizes it – there is only one source for the calculation
¾ facilitates multiple use in the Excel workbook or in VBA code
¾ allows for appropriate documentation, including restrictions on use and
limiting assumptions
¾ opens the door for further packaging of families of functions into libraries
called Add-Ins.
The general syntax for the VBA program definition of a user-defined function is
Function name (argument list)
VBA program code
name = result
End Function
An important observation of the above is that the result of the function calculation
must be assigned to the name of the function.
The Function statement can have additional features
It can be preceded by the qualifiers
Private
or
Public
The scope of the function is restricted to the module in which it resides.
The scope of the function includes all open Excel projects. Public is
the default.
User-defined Functions
Static
Variables used in the function retain their values from one call of the
procedure to the next.
and can have appended
As type
This specifies the data type of the result returned by the function.
For our cone example above, the function might be programmed as
Function ConeArea(radius,height)
Dim pi as Single
pi=4*atn(1)
ConeArea=pi*radius^2+pi*radius*Sqr(radius^2+height^2)
End Function
The VBA code in the Visual Basic Editor window would look like
and using the function on the Excel spreadsheet is shown below with the result and
the formula.
If the function is of the public type, it can be accessed from the Insert Function
button in Excel,
. The ConeArea function can be found under the User
Defined category, as shown in the Insert Function window below.
2-
User-defined Functions
The arguments shown, radius and height, are the names used in the VBA code for
the arguments in the Function statement. As you can see, it is good practice to use
meaningful names in your code.
You will note in this window the statement “No help available.” Some help text can
be added conveniently using the Macro dialog window. To do this, select Tools →
Macro → Macros from Excel, type in the name of the function, and click on the
Options button.
In the Macro Options window, type in the help text:
3-
User-defined Functions
and click OK. Now, when the Insert Function window is present and the ConeArea
function is selected, the help text will appear.
Clicking the OK button will yield the Function Arguments window:
and the help text is again displayed. As argument values or references are entered
into the fields, their values are displayed to the right, and, when possible, the
function value is displayed below the argument values. There is no explanation
given for the arguments though, such as Height shown here.
User-defined functions can be more complex, for example, implementing numerical
methods that might have been prototyped on the Excel spreadsheet.
The file ExcelInterp.xls implements automatic linear interpolation on the
spreadsheet. A user-defined function can be added to this workbook that
accomplishes the same linear interpolation, but in a better, more flexible way.
4-
User-defined Functions
A new module is inserted using the Visual Basic Editor. The following code
implements the ViscInterp function:
Option Explicit
Function ViscInterp(DegF) As Single
Dim LowRow,HighRow,RowIndex As Integer
Dim TempLow, TempHigh, ViscLow, ViscHigh As Single
If DegF < Range("B10") Then
ViscInterp = Range("D10")
ElseIf DegF >= Range("B22") Then
ViscInterp = Range("D22")
Else
For RowIndex = 11 To 22
If Cells(RowIndex, "B") > DegF Then
LowRow = RowIndex - 1
HighRow = RowIndex
Exit For
End If
Next RowIndex
TempLow = Cells(LowRow, "B")
TempHigh = Cells(HighRow, "B")
ViscLow = Cells(LowRow, "D")
ViscHigh = Cells(HighRow, "D")
ViscInterp=(DegF-TempLow)/(TempHigh-TempLow)*(ViscHigh-ViscLow)+ViscLow
End If
End Function
On the Excel spreadsheet, in cell F20, the following formula tests the UDF:
=ViscInterp(Temp)
If your UDF works, it should display the same result as is shown in cell F18, perhaps
with a few more significant figures.
The ViscInterp function makes use of physical property data stored on the
spreadsheet. It would also be possible to store the data local to the function. As
another type of enhancement, it would be possible to generalize the interpolation
function so that the property table becomes an argument. Then any table could be
used. This use of array arguments is now illustrated.
In a new, blank workbook, the numbers shown below are entered.
5-
User-defined Functions
Since Excel is used frequently to manage numbers in rectangular arrays like this, it
is useful to know how to access and manipulate arrays in VBA too. There are two
ways to access or transfer an array of values to VBA:
¾ as an argument to a function
¾ by using a Range object in function
First, we develop a function that returns the maximum value of in an array of cells
which is the argument to the function.
Option Explicit
Option Base 1
Function MaxVal(DataArray)
Dim NumRows, NumCols, i, j As Integer
MaxVal = DataArray(1, 1)
NumRows = DataArray.Rows.Count
NumCols = DataArray.Columns.Count
For i = 1 To NumRows
For j = 1 To NumCols
If DataArray(i, j) > MaxVal Then
MaxVal = DataArray(i, j)
End If
Next j
Next i
End Function
On the spreadsheet, and, in cell D7, a formula is entered to test the MaxVal function:
=MaxVal(A1:F4)
Now look back at the VBA code and study the following observations:
a) The Option Base 1 directive is used prior to the Function statement so that
array subscripts will start with 1 and not with 0.
b) The function result, MaxVal, is first set to the 1,1 element of the input array.
This is the element in the upper left-hand corner, in the first row and the first
column.
c) The Count property is used to determine the number of rows and columns in
the input array, and the local variables NumRows and NumCols are used to
store these values.
d) Two For...Next loops, one nested inside the other, range through all the
possible combinations of row and column index values.
6-
User-defined Functions
e) The If statement checks to see whether an element of the array is greater
than the current value of MaxVal. If so, it sets MaxVal to this element.
f) The loops then continue checking values in the array until all are checked.
A second function called MinVal is created that determines the minimum value in the
input array1. Test the function by entering this formula in cell D8:
=MinVal(A1:F4)
Now, the function ArrayRange2 is created making use of the two functions, MaxVal
and MinVal, that you have already created.
Function ArrayRange(DataArray)
ArrayRange = MaxVal(DataArray) - MinVal(DataArray)
End Function
A formula is entered in cell D9 back on the spreadsheet:
=ArrayRange(A1:F4)
The functions described above all return a single result in the name of the function.
It is possible for a function to return more than one result. There are many
engineering and scientific applications where this is convenient. This type of
function is called an array function. This is illustrated with the creation of a simple
array function. In a new workbook the numbers as shown below are entered:
Let's say we wanted to have a function called PctVal that would display the
proportion in % of each of these numbers to the total of the numbers. This function
would have to display its results in 4 cells.
In the VBE, a new module is inserted and the function shown below:
Option Explicit
Option Base 1
Function PctVal(ValueArray)
Dim NumRows, i As Integer
1
2
One can save some time here by copying code from MaxVal to MinVal.
If we use the name Range for the function, that will get in the way of VBA's Range object.
7-
User-defined Functions
Dim Pct() As Single
Dim Sum As Single
NumRows = ValueArray.Rows.Count
ReDim Pct(NumRows)
Sum = 0
For i = 1 To NumRows
Sum = Sum + ValueArray(i)
Next i
For i = 1 To NumRows
Pct(i) = ValueArray(i) / Sum * 100
Next i
PctVal = Application.WorksheetFunction.Transpose(Array(Pct))
End Function
Back on the spreadsheet the cells B1:B4 are selected, and the formula is entered as
shown in the figure below.
Since this is an array formula, it must be entered with Ctrl-Shift-Enter. The column B
cells should then show the percentage proportions of the values in the A column.
Here are a few comments about the Function PctVal code:
a) The Dim Pct() As Single statement sets up a local variable as an array, but
without specifying the dimensions of the array yet. This is called a "dynamic"
array. Later in the code, after the number of rows in the input array has been
determined (NumRows), the Pct array is dimensioned to that number using the
ReDim statement.
b) The sum of the elements in the input array is accumulated in the Sum variable
using a For...Next loop. This sum is needed for the following For...Next loop
where the percentages are computed.
c) Finally, the percentages are formed into a "variant array" that can be assigned to
the name of the function, PctVal. The Array function is used for this, but, if only
the Array function were used, the result would appear in a row of cells. Since we
want the result to appear in a column of cells, we use Excel's Transpose function
to reorder the row into a column.
The following is a more sophisticated example of the use of an array function to
compute a 3-component vapor-liquid equilibrium.
8-
User-defined Functions
Background for Multicomponent Equilibrium Spreadsheet Calculations3
At low pressures (up to a few atmospheres)
pi0
yi = xiγ i
P
Note: Fugacity coefficients are assumed to cancel each other (or nearly) and
Poynting corrections are assumed unity (or close).
yi : vapor mole fraction of component i
xi : liquid mole fraction of component i
γ i : activity coefficient of component i
where
pi0 : vapor pressure of component i
P : total pressure
Vapor pressure is typically predicted by an Antoine equation:
log10 ⎡⎣ pi0 ⎤⎦ = A −
where
B
T +C
A,B,C : constants
T:
temperature, °C
Activity coefficients are predicted by one of three methods: Wilson, NRTL, or
UNIQUAC. References for these methods are:
Wilson:
Wilson, G. M., J. Amer. Chem. Soc., 86, 127 (1964).
NRTL:
Renon, H., and J. M. Prausnitz, AIChE J, 14, 135 (1968).
UNIQUAC: Abrams, D. S., and J. M. Prausnitz, AIChE J, 21, 116 (1975).
Wilson Equation
m
⎛ m
⎞
xΛ
ln γ i = − ln ⎜ ∑ x j Λij ⎟ + 1 − ∑ m k ki
k =1
⎝ j =1
⎠
∑x Λ
j =1
j
kj
where
Λij =
⎡ λij − λii ⎤
exp ⎢ −
Vi
RT ⎥⎦
⎣
V jL
L
3
Gmehling, J., and U. Onken, Vapor-Liquid Equilibrium Data Collection, Aqueous-Organic Systems, DECHEMA
Chemistry Data Series, Vol. 1, Part 1, DECHEMA, 1977, ISBN 3-921567-01-7.
9-
User-defined Functions
Λii = Λ jj = 1
and
Nomenclature
Vi L :
λij :
interaction energy between components i and j, λij = λ ji
T:
R:
absolute temperature, typ. K
gas law constant
molar volume of pure liquid component i
NRTL Equation
m
ln γ i =
∑τ
j =1
m
ji
G ji x j
∑G x
l =1
li i
m
⎛
xnτ nj Gnj
∑
m
x j Gij ⎜
n =1
⎜
τ ij − m
+∑ m
⎜
j =1
Glj xl ⎜
Glj xl
∑
∑
l =1
l =1
⎝
⎞
⎟
⎟
⎟
⎟
⎠
See note below.
where
τ ji =
g ji − gii
RT
,
τ ii = τ jj = 0
G ji = exp ⎡⎣ −α jiτ ji ⎤⎦ ,
G jj = Gii = 1
Nomenclature
gij :
parameter for interaction between components i and j, gij = g ji
α ij :
non-randomness parameter, α ij = α ji
Note: there is a typo in the NRTL equation printed in the reference.
UNIQUAC Equation
ln γ i = ln γ iC + ln γ iR
where
ln γ iC = ln
ϕi
ϑ
ϕ
z
+ qi ln i + li − i
xi 2
xi
ϕi
∑x l
j j
j
and
10-
User-defined Functions
⎡
⎤
m
m
⎢
⎥
ϑ
τ
⎛
⎞
j ij
⎥
ln γ iR = qi ⎢1 − ln ⎜ ∑ ϑ jτ ji ⎟ − ∑ m
⎢
⎝ j =1
⎠ j =1 ∑ ϑ τ ⎥
k kj
⎢⎣
⎥⎦
k =1
with
li =
z
( ri − qi ) − ( ri − 1) ,
2
⎡ u ji − uii ⎤
,
RT ⎥⎦
⎣
τ ji = exp ⎢ −
z = 10
τ ii = τ jj = 1
Nomenclature
qi :
ri :
uij :
area parameter of component i
volume parameter of component i
parameter of interaction between components i and j, uij = u ji
z:
coordination number
combinatorial part of activity coefficient of component i
γ :
γ iR :
C
i
residual part of activity coefficient of component i
qx
area fraction of component i
ϑi = i i :
∑ qjxj
j
ϕi =
ri xi
:
∑ rj x j
volume fraction of component i
j
An Excel spreadsheet, VLE.xls, illustrates the calculation of the vapor-liquid
equilibrium for a mixture of acetone, 2-propanol and water. As an illustration of the
use of an array function, the NRTL case is computed with the function NRTL. The
VBA function replicates the calculation on the spreadsheet, hiding the complexity of
the iterative calculation in the VBA code.
11-
User-defined Functions
Option Explicit
Option Base 1
Function NRTL(x, Aij, Aji, alpha, P, Tguess, Ap, Bp, Cp)
Dim T1, T2, T3
Dim y(3), yE1, yE2
T1 = Tguess
Do
Call yError(x, Aij, Aji, alpha, P, T1, Ap, Bp, Cp, y, yE1)
T2 = T1 + 0.1
Call yError(x, Aij, Aji, alpha, P, T2, Ap, Bp, Cp, y, yE2)
T3 = T1 - yE1 * 0.1 / (yE2 - yE1)
If Abs(T3 - T1) < 0.001 Then
Exit Do
Else
T1 = T3
End If
Loop
NRTL = Application.Transpose(Array(y(1), y(2), y(3), T3))
End Function
Sub yError(x, Aij, Aji, alpha, P, T, Ap, Bp, Cp, y, yE)
Dim Vp(3), A(3, 3), tau(3, 3), G(3, 3), Alph(3, 3)
Dim Sum1, Sum2, Term1(3), Term2(3, 3), Sum3(3)
Dim lngam(3), gam(3)
Dim R, TK
Dim i, j, l, n As Integer
R = 1.98721
TK = T + 273.15
For i = 1 To 3
A(i, i) = 0
Next i
A(2, 1) = Aji(1)
A(3, 1) = Aji(2)
A(3, 2) = Aji(3)
A(1, 2) = Aij(1)
A(1, 3) = Aij(2)
A(2, 3) = Aij(3)
For i = 1 To 3
Alph(i, i) = 0
Next i
Alph(2, 1) = alpha(1)
Alph(3, 1) = alpha(2)
Alph(3, 2) = alpha(3)
Alph(1, 2) = alpha(1)
Alph(1, 3) = alpha(2)
Alph(2, 3) = alpha(3)
For i = 1 To 3
For j = 1 To 3
tau(i, j) = A(i, j) / R / TK
Next j
Next i
12-
User-defined Functions
For i = 1 To 3
For j = 1 To 3
If i = j Then
G(i, i) = 1
Else
G(i, j) = Exp(-Alph(i, j) * tau(i, j))
End If
Next j
Next i
For i = 1 To 3
Sum1 = 0
For j = 1 To 3
Sum1 = Sum1 + tau(j, i) * G(j, i) * x(j)
Next j
Sum2 = 0
For l = 1 To 3
Sum2 = Sum2 + G(l, i) * x(l)
Next l
Term1(i) = Sum1 / Sum2
For l = 1 To 3
Sum3(l) = 0
Next l
For j = 1 To 3
Sum1 = 0
For l = 1 To 3
Sum1 = Sum1 + G(l, j) * x(l)
Next l
Sum2 = 0
For n = 1 To 3
Sum2 = Sum2 + x(n) * tau(n, j) * G(n, j)
Next n
Term2(i, j) = x(j) * G(i, j) / Sum1 * (tau(i, j) - Sum2 / Sum1)
Sum3(i) = Sum3(i) + Term2(i, j)
Next j
lngam(i) = Term1(i) + Sum3(i)
gam(i) = Exp(lngam(i))
Next i
For i = 1 To 3
Vp(i) = 10 ^ (Ap(i) - Bp(i) / (T + Cp(i))) * 101325 / 760
Next i
Sum1 = 0
For i = 1 To 3
y(i) = x(i) * Vp(i) / P * gam(i)
Sum1 = Sum1 + y(i)
Next i
yE = 1 - Sum1
End Sub
The implementation of the NRTL on the spreadsheet is shown below.
13-
User-defined Functions
Now that this function is implemented it can be used in other spreadsheet
applications, such as a flash calculation.
Multicomponent Flash Drum Calculation
Reference: Excel workbook file Flash.xls. Cell addresses shown below in []
A multicomponent mixture is flashed through
a valve into a drum. The liquid and vapor
products are removed through valved lines.
The spreadsheet carries out a steady-state Feed
design calculation, including the sizing of
the three control valves.
Vapor
The feed is specified by its composition,
temperature, pressure and flow rate.
Liquid
The system chosen here is:
acetone, 2-propanol, water
and the feed specifications are:
composition, zi ,i = 1,2,3 , mole fraction: 0.294, 0.484, 0.222 [B4:B6]
temperature: 80°C
pressure:
20 psia
flow rate:
5 kg/s
[B12]
[B11]
[B8]
The density of a liquid mixture of these components can be predicted from the
formula
3
ρ=
∑ mw z
i =1
3
i i
∑a z
i =1
[B13]
where mwi are the molecular weights, 58.08, 60.09,
i i
18.016,
respectively, and ai are constant parameters, 0.07947, 0.08448, 0.01795,
respectively.
This equation is fit by nonlinear regression on the Density Data worksheet and the
performance of the fit is presented on the Density Chart sheet. The density
"experimental" values were obtained from the HYSYS property package.
This allows the feed volumetric flow rate to be calculated. [B9:B10]
The feed mass fractions are obtained from the mole fractions and the molecular
weights:
14-
User-defined Functions
zi′ =
mwi zi
[D4:D6]
3
∑ mw
j =1
j
The heat capacity of the feed mixture is computed by a weighted (by mole fraction)
sum of the component heat capacities at the feed temperature.
3
CP = ∑ CPi zi [B17]
i =1
The component heat capacities are
acetone:
⎡ kJ
⎤
CP1 ⎢
⎥ = 123 + 0.186T [°C ]
⎣ kgmol K ⎦
[B14]
2-propanol:
⎡ kJ
⎤
CP 2 ⎢
⎥ = 178.1 + 0.3066T [°C ]
⎣ kgmol K ⎦
[B15]
This equation is developed by linear regression on the Heat Capacity sheet.
⎡ kJ
⎤
CP3 ⎢
⎥ = 75.4
⎣ kgmol K ⎦
water:
[B16]
And the feed enthalpy is estimated by
h f = C pf T f
4
[B28]
The feed valve is designed from the nominal design equation
CV =
q [ gpm ]
∆P [ psi ]
SpGr
[B20]
with a design ∆P of 5.3 psi
[B19]
The above result would be used to specify the valve to a vendor.
A dimensional sizing of the valve is done with the formula
4
Note: this might be better done by integrating the heat capacities from 0°C to the feed temperature.
15-
User-defined Functions
⎡ m3 ⎤
q⎢ ⎥
⎣ s ⎦
CV′ ⎡⎣ m 2 ⎤⎦ =
∆P [ Pa ]
⎡ kg ⎤
ρ⎢ 3⎥
⎣m ⎦
[B21]
The valve is designed to be 25% open ( f ) at the nominal design feed flow rate.
[B22:B23]
With an =% trim and rangeability ( R ) of 50:1 [B24], this requires a stem position ( l )
of 0.646, according to the formula
f ( l ) = R l −1
[B25]
The flash vessel is designed on the basis of a 5-minute holdup of feed liquid
[E10:E11]. This gives a liquid volume of just over 2 m3 [E12]. An inside diameter of
1.5 m is chosen [E13] giving a nominal liquid depth of 1.15 m [E15]. The actual
vessel is sized to a height of 2.5 m [E16] to allow sufficient vapor space and
freeboard.
The operating pressure of the vessel is selected as 1 atm [E18:E20]. The ideal gas
law is used to compute the number of moles [E25] and the mass [E26] of the vapor
in the vessel. The latter makes use of the average molecular weight of the vapor
[M3], which comes from having calculated the vapor-liquid equilibrium. That is
discussed below.
The flash calculation block makes use of vapor-liquid equilibrium and the enthalpy
and component balances on the vessel to determine the compositions and flow rates
of the liquid and vapor streams leaving the vessel. This is an iterative calculation
and proceeds from the estimation of the liquid flow rate [H5] and two liquid
compositions [H6:H7]. The vapor flow rate can then be calculated from the overall
mass balance [H9].
The composition of the liquid is known, now that it has been estimated. On the VLE
sheet, the NRTL function is called to carry out the VLE calculation [D10:D13],
returning the vapor compositions and the equilibrium temperature. The enthalpies of
the vapor and liquid streams are then calculated back on the Main sheet. First, the
heat capacities and heats of vaporization are calculated [H14:I16]. Then the liquid
and vapor stream enthalpies are calculated [H20:I21]. To get these on a mass
basis, the average molecular weights are needed [H25:H27]. The enthalpy [L9] and
two of the component balances [L10:L11] are then check calculated5 and a sum-ofsquares of balance errors is computed [L12]. The Solver is then used to adjust the
5
Note: the enthalpy balance is scaled down by a factor of 1000, so it's equation error will be commensurate
with those of the component mole balances.
16-
User-defined Functions
original estimates of liquid flow rate [H5] and liquid compositions [H6:H7] to close the
balances.
With the knowledge of the liquid flow rate and compositions, the liquid control valve
is sized in a similar fashion to the feed valve. The vapor valve is also sized but that
uses a compressible-flow sizing equation,
CV =
wV
2.8KC f P G f ( y − 0.148 y 3 )
Ref6
[N24]
where
wV :
K:
Cf :
mass flow rate, kg/s
[H9]
units conversion factor, 5.117x108
critical flow factor, depending on valve type, 0.9 chosen here
P:
upstream pressure, absolute, Pa, here, vessel pressure [N18:N19]
G=
mw
29
Gf = G
y=
gas specific gravity at 1 atm and 60°F
289
T [K ]
1.63 ∆P
Cf
P
[N20]
[N22]
gas specific gravity at flowing temperature [N23]
critical/subcritical flow factor
[N21]
For the vapor valve, nominal flow rate is chosen for 25% open and an =% trim is
selected.
It is good to get into the habit of creating user-defined functions. This habit can lead
to the creation of collections of functions with a common theme. It is convenient to
package such collections in an “Add-In”.
In the illustration below, an Excel add-in is created from a family of useful userdefined functions.
Starting with a blank workbook, the VBE is accessed and a new module is inserted.
The following code is entered:
Option Explicit
Function FtoC(degF) As Single
FtoC = (degF - 32) / 1.8
End Function
6
Smith & Corripio, Principles and Practice of Automatic Process Control, Wiley, 1985, p. 142.
17-
User-defined Functions
As you can probably tell, this is a function to convert °F to °C. Returning to the Excel
spreadsheet environment, a formula is entered in a cell to test the function, like
=FtoC(212)
Now, returning to the module in the VBE, a function CtoF is added that converts °C
to °F.
This procedure is repeated to add the following functions to the same module:7
CtoK
KtoC
lbtokg
kgtolb
Ltogal
galtoL
convert °C to Kelvins
convert K to °C
convert pounds to kilograms
convert kilograms to pounds
convert liters to gallons
convert gallons to liters
All functions are tested on the spreadsheet to make sure they work correctly. Once
this is complete, all formulas are deleted from the spreadsheet. This should leave a
blank workbook with a VBA Project module that contains all 8 functions.
Now, the workbook file is saved in a special way. The F12 key is pressed to get the
Save As dialog box. Change the Save As Type: field to Microsoft Excel Add-In
(*.xla). It is the last item on the drop-down list. A name, such as Units.xla in the File
name: field. Then the Save button is clicked. This workbook is now closed. A new,
blank workbook is opened, and, from the Tools menu, Add-Ins... is clicked. The
Add-Ins dialog box should appear. The Browse button is clicked and the Units.xla
file is located. That file is double-clicked. This installs the add-in and thefamily of
functions should now be available. Once installed, the functions will be available to
any open workbook. The add-in should then appear in the list of the dialog box.
Formulas like the following can be entered to test the functions:
7
B2:
B3:
=galtoL(1)
=Ltogal(4)
B5:
B6:
=lbtokg(295)
=kgtolb(110)
D2:
D3:
=CtoK(0)
=KtoC(0)
D5:
D6:
=FtoC(375)
=CtoF(-40)
It may be useful to access the SIUnitsforChEs.xls workbook for conversion factors.
18-
© Copyright 2026 Paperzz