I will have to admit that I'm not fully conversant
with the latest developments in Fortran.
But it is my understanding that starting around the
time of Fortran 90, constructs have been added to
the language so that programs can be written to
efficiently make use of the vector arithmetic features
of computers like the Cray I, and its successors and
imitators.
These days, except for the SX-Aurora TSUBASA from
NEC, most vector arithmetic on computers belongs to
a line of development that started from the AN/FSQ-7
for SAGE rather than the one that went through the
Cray I.
I remember that back in the 1970s I mentioned to someone
that I thought this sort of thing would be a nice feature to
have, even without vector hardware. The fellow replied that
I was being too slavish in following APL, matrix multiplication
would be better.
Well, I agreed that matrix multiplication was useful in
scientific and engineering computation.
But I felt that this would assign inappropriate semantics
to the construct of an array in Fortran.
Of course, one can add a built-in subroutine for matrix
multiplication.
But more recently, I thought that there might be a better
solution; and now on this newsgroup, I've thought to
present it more widely.
Fortran has a COMPLEX data type.
Why can't it have a MATRIX data type?
So there would be a MATRIX statement, having much the
same syntax as a DIMENSION statement. It could be used
to create one-dimensional vectors and matrices that were
not square.
I will have to admit that I'm not fully conversant
with the latest developments in Fortran.
..
Why can't it have a MATRIX data type?
..
If A, B, and C are declared as matrices (or one of B or C is
a scalar) then
A = B * C
would perform matrix multiplication as expected... ..
Is this not a useful thing to include in Fortran?
Maybe someone else has even suggested it already?
..
On Monday, October 25, 2021 at 11:15:12 PM UTC-7, Quadibloc wrote:
I will have to admit that I'm not fully conversant
with the latest developments in Fortran.
But it is my understanding that starting around the
time of Fortran 90, constructs have been added to
the language so that programs can be written to
efficiently make use of the vector arithmetic features
of computers like the Cray I, and its successors and
imitators.
Complicated question.
In the Cray days, there was much work on getting compilers to figure
out how to make good use of vector registers.
The Fortran added array expressions, which are, among others, supposed
to make it easier for vector processors. There is also FORALL, which was more
specifically added to allow for vector processors.
Except that both don't work quite that well.
Both array expressions and FORALL are defined such that the whole
right hand side is evaluated before any changes are made to the left
side. Unless vector registers are big enough for the whole array, that
can mean temporary arrays.
Even worse, many array expressions, as written, are harder for compilers
to optimize well, than the DO form. That is especially true when one can leave a computation early.
And then vector processors went out of style.
Complicated question.
In the Cray days, there was much work on getting compilers to figure
out how to make good use of vector registers.
The Fortran added array expressions, which are, among others, supposed
to make it easier for vector processors. There is also FORALL, which was more
specifically added to allow for vector processors.
Wasn't FORALL rather designed for general parallelism, allowing out of
order execution of the loop ?
Except that both don't work quite that well.
Both array expressions and FORALL are defined such that the whole
right hand side is evaluated before any changes are made to the left
side. Unless vector registers are big enough for the whole array, that
can mean temporary arrays.
Or unless the compiler can determine that this is safe to update the LHS during the evaluation of the RHS. But yes, this means more complexity on
the compiler side.
Even worse, many array expressions, as written, are harder for compilers
to optimize well, than the DO form. That is especially true when one can leave a computation early.
And then vector processors went out of style.
Yes, and no. Most of modern CPUs have small vector registers.
It seems a lot of work, just to get the * operator to change.
On Tuesday, October 26, 2021 at 4:47:28 AM UTC-6, gah4 wrote:
It seems a lot of work, just to get the * operator to change.
Your comment has made me think of a potential pitfall:
using operator overloading for matrix multiplication versus element-by-element multiplication has the potential to cause
confusion, and, hence, errors in programming.
The reason I was doing it was not so much to have *access*
to matrix multiplication - as has been noted, Fortran now has a
function for that - but instead to have the language treat matrices consistently as mathematical objects which resemble numbers,
just as it does with complex numbers.
That would be a very natural style of writing certain types of
programs which made extensive use of matrices.
On Tuesday, October 26, 2021 at 5:14:10 PM UTC-7, Quadibloc wrote:
On Tuesday, October 26, 2021 at 4:47:28 AM UTC-6, gah4 wrote:
It seems a lot of work, just to get the * operator to change.
Your comment has made me think of a potential pitfall:Octave and Matlab, two languages designed around matrix
using operator overloading for matrix multiplication versus element-by-element multiplication has the potential to cause
confusion, and, hence, errors in programming.
operations, used * for the matrix multiply operator,
and .* for the element wise multiply operator.
That is slightly more obvious for an interpreted language,
and also with the matrix emphasis.
While matrix math is not rare in Fortran, it is also not the
primary use for the language.
Octave and Matlab, even more, have the / operator (multiply
by the inverse matrix), and ** (matrix exponential), again
with the ./ and .** element wise operators.
The reason I was doing it was not so much to have *access*I don't know if there was any thought to that when array expressions
to matrix multiplication - as has been noted, Fortran now has a
function for that - but instead to have the language treat matrices consistently as mathematical objects which resemble numbers,
just as it does with complex numbers.
were added, but I suspect not. Adding new operators doesn't seem
like such a bad idea, though.
I think I still remember, when I first learned C, how convenient it
was to have the % (modulo) operator, instead of the MOD function.
I suspect I do a lot more modulo than matrix multiplication, though.
That would be a very natural style of writing certain types of
programs which made extensive use of matrices.
c) Starting with Fortran 90 introduced nearly 30 years ago, the
language support for the outstanding "matrix" operation you show is
A = matmul( B, C )
with the details of achieving efficient matrix multiplication
left mostly to the processors.
On Tuesday, October 26, 2021 at 4:47:28 AM UTC-6, gah4 wrote:
It seems a lot of work, just to get the * operator to change.
Your comment has made me think of a potential pitfall:
using operator overloading for matrix multiplication versus element-by-element multiplication has the potential to cause
confusion, and, hence, errors in programming.
The reason I was doing it was not so much to have *access*
to matrix multiplication - as has been noted, Fortran now has a
function for that - but instead to have the language treat matrices consistently as mathematical objects which resemble numbers,
just as it does with complex numbers.
That would be a very natural style of writing certain types of
programs which made extensive use of matrices.
John Savard
FortranFan wrote:
c) Starting with Fortran 90 introduced nearly 30 years ago, the
language support for the outstanding "matrix" operation you show is
A = matmul( B, C )
with the details of achieving efficient matrix multiplication
left mostly to the processors.
If there were to be a semi-serious attempt to make matrices more easily
used in Fortran, one might contemplate adding optional arguments to
MATMUL to allow optional transposition of the input arguments before
being used in the matrix-multiplication. This would then allow
avoidance of unnecessary construction of transposes.
FortranFan wrote:
c) Starting with Fortran 90 introduced nearly 30 years ago, the
language support for the outstanding "matrix" operation you show is
A = matmul( B, C )
with the details of achieving efficient matrix multiplication
left mostly to the processors.
If there were to be a semi-serious attempt to make matrices more easily
used in Fortran, one might contemplate adding optional arguments to
MATMUL to allow optional transposition of the input arguments before
being used in the matrix-multiplication. This would then allow
avoidance of unnecessary construction of transposes.
In the Cray days, there was much work on getting compilers to figure
out how to make good use of vector registers.
The Fortran added array expressions, which are, among others, supposed
to make it easier for vector processors. There is also FORALL, which was more
specifically added to allow for vector processors.
Wasn't FORALL rather designed for general parallelism, allowing out of
order execution of the loop ?
It was about the time of Cray, and I thought I knew that it was related
to vector processors.
Now we have DO CONCURRENT that, as far as
I know, is related to out of order.
On 10/27/21 7:30 AM, David Jones wrote:...
FortranFan wrote:
... There is currently no way to do a
pointer assignemnt to the transpose of an array -- this would allow that
in a straightforward way. The programmer could do things like >
B => shallow_transpose(A)
shallow_transpose(B) => A
B = expression...shallow_transpose(A)...expression
B = function(,..., shallow_transpose(A), ...)
call some_subroutine(..., shallow_transpose(A), ...)
and know that all of those transpose operations would be done with O(1) effort.
I will have to admit that I'm not fully conversant.
with the latest developments in Fortran.
But it is my understanding that starting around the
time of Fortran 90, constructs have been added to
the language so that programs can be written to
efficiently make use of the vector arithmetic features
of computers like the Cray I, and its successors and
imitators.
These days, except for the SX-Aurora TSUBASA from
NEC, most vector arithmetic on computers belongs to
a line of development that started from the AN/FSQ-7
for SAGE rather than the one that went through the
Cray I.
Be that as it may...
So, IIRC, one can write
A = B * C
in Fortran today and if A, B, and C are arrays having the
same dimensions (or if one of B or C is a scalar) it will
do an element-by-element multiplication, much as would
have been done by APL, without the need for a DO loop.
I remember that back in the 1970s I mentioned to someone.
that I thought this sort of thing would be a nice feature to
have, even without vector hardware. The fellow replied that
I was being too slavish in following APL, matrix multiplication
would be better.
Well, I agreed that matrix multiplication was useful in.
scientific and engineering computation.
But I felt that this would assign inappropriate semantics
to the construct of an array in Fortran.
Of course, one can add a built-in subroutine for matrix
multiplication.
But more recently, I thought that there might be a better.
solution; and now on this newsgroup, I've thought to
present it more widely.
Fortran has a COMPLEX data type.
Why can't it have a MATRIX data type?
So there would be a MATRIX statement, having much the.
same syntax as a DIMENSION statement. It could be used
to create one-dimensional vectors and matrices that were
not square.
If A, B, and C are declared as matrices (or one of B or C is.
a scalar) then
A = B * C
would perform matrix multiplication as expected...
if one of.
B or C is a one-dimensional matrix, it would be treated as
either a row or column vector, whichever one would make
sense. (Unless that isn't practical; declaring 1 by n and n by 1
matrices instead to make it explicit is certainly an option as
well.)
Since a matrix is an a x b grid of floating-point numbers
combined into an object with specific mathematical.
properties, while it would be possible to create an array
of matrices, it would *not* be possible to create a matrix
the elements of which are arrays.
For much the same reason that you can't make a
complex number the real and imaginary parts of which
are arrays of the same shape.
But, yes, it should be allowed to create a matrix the elements
of which are complex numbers.
(Or quaternions, for that.
matter!)
Oh, and by the way: yes, it should be possible to take a (4,4)
matrix the elements of which are REAL*16 and EQUIVALENCE
it to a (4,4) array the elements of which are REAL*16
with the.
expected results of the same-numbered elements perfectly
corresponding. (This might even be a reason not to deprecate
EQUIVALENCE...)
Is this not a useful thing to include in Fortran?
Maybe someone else has even suggested it already?
On 21/10/27 7:17 PM, Ron Shepard wrote:
On 10/27/21 7:30 AM, David Jones wrote:...
FortranFan wrote:
...
... There is currently no way to do a pointer assignemnt to the
transpose of an array -- this would allow that in a straightforward
way. The programmer could do things like >
B => shallow_transpose(A)
shallow_transpose(B) => A
B = expression...shallow_transpose(A)...expression
B = function(,..., shallow_transpose(A), ...)
call some_subroutine(..., shallow_transpose(A), ...)
and know that all of those transpose operations would be done with
O(1) effort.
This came up some time ago in the thread "Mathematics of arrays".
The minimum "hacking" solution (dangerous!) I found in gfortran
was the following: (just to play with it, not really useful..)
To be included in a module or "contains" of main program
!
function shallow_transpose(p1) result(p2)
real(wp), target, intent(in) :: p1(:,:)
real(wp), pointer :: p2(:,:)
type dummy
real(wp), pointer :: p(:,:)
end type
type(dummy) :: d
integer :: ar(22) ! to fetch descriptor.. yes, that big!
d%p => p1; ! look at pointer p1
ar = transfer(d, ar) ! fetch descriptor in ar(:)
ar(11:22) = [ar(17:22), ar(11:16)] ! swap index information
d = transfer(ar, d) ! put back in the descriptor
p2 => d%p ! copy pointer from wrapper
end
But just from looking at what it does, that is exactly what I have in
mind for the shallow_transpose() function, but in a standard and
portable way.
In the 1970s, the facility had already been available in PL/I
for at least 4 years.
On Friday, October 29, 2021 at 6:44:30 PM UTC-6, Robin Vowels wrote:
In the 1970s, the facility had already been available in PL/I
for at least 4 years.
I wasn't claiming originality. It was a good idea in PL/I,
and so it would be good for Fortran to have it too.
Just as later on, in Fortran 77, block-structured IF statements
were added, which PL/I also had.
On Sunday, October 31, 2021 at 7:56:12 PM UTC-7, Quadibloc wrote:.
On Friday, October 29, 2021 at 6:44:30 PM UTC-6, Robin Vowels wrote:
In the 1970s, the facility had already been available in PL/I
for at least 4 years.
I wasn't claiming originality. It was a good idea in PL/I,
and so it would be good for Fortran to have it too.
Just as later on, in Fortran 77, block-structured IF statements
were added, which PL/I also had.
One interesting difference, though.
Fortran array assignments require the "as if" the whole right hand side
is evaluated before the left side is changed. If the compiler can't be sure, it has to use a temporary array.
PL/I array assignment is done element by element.
If an array element changes,.
that change is used in later assignments. That was before.
vector processors, maybe before people thought about building them.
On 10/30/2021 4:35 PM, Ron Shepard wrote:
But just from looking at what it does, that is exactly what I have in
mind for the shallow_transpose() function, but in a standard and
portable way.
Um, no. This is the absolute antithesis of "standard and portable".
It
relies on a specific compiler's implementation of pointers, something
that can change even between versions of a compiler.
It will not work
with other compilers.
On 21/10/31 3:25 PM, Steve Lionel wrote:
On 10/30/2021 4:35 PM, Ron Shepard wrote:
But just from looking at what it does, that is exactly what I have in
mind for the shallow_transpose() function, but in a standard and
portable way.
Um, no. This is the absolute antithesis of "standard and portable".
I'm not entirely sure about this "absolute", because somehow I could
perhaps have used equivalence, instead of the transfer function. But
I admit this was close to what is conceivably possible (in terms of approximating the antithesis, I mean..)
It relies on a specific compiler's implementation of pointers,
something that can change even between versions of a compiler.
That is true, but it did not rely on possible alignment problems, which equivalence could perhaps have introduced (with some care..) That could
also easily have provided out-of-bounds addressing, which was ow not
used at all!
It will not work with other compilers.
OK, it did at least satisfy that basic requirement. :^)
[But seriously, I think Ron is right that it could be portable if each version of a compiler would do this in the way appropriate for, well..
that version of the compiler! After all that is how all code generation
is kept portable, even though "a specific compiler's implementation"
that "can change between versions" is used for almost everything.]
I guess my wording in the previous post must have been confusing. I
certainly was not saying that that implementation of the
shallow_transpose() function was portable. In fact, I noted that it did
not work with an older version of gfortran, so it was not even portable between different versions of the same compiler. But that implementation
does demonstrate that such an intrinsic function could provide that functionality in a standard way that would be portable to the
programmer. The low-level implementation of that intrinsic function
would of course depend on the underlying metadata that the compiler uses
to describe arrays.
I wasn't especially trying to follow it, but I thought I remembered
something about standardizing the descriptors, such that one could
do things like this.
I will have to admit that I'm not fully conversant
with the latest developments in Fortran.
But it is my understanding that starting around the
time of Fortran 90, constructs have been added to
the language so that programs can be written to
efficiently make use of the vector arithmetic features
of computers like the Cray I, and its successors and
imitators.
These days, except for the SX-Aurora TSUBASA from
NEC, most vector arithmetic on computers belongs to
a line of development that started from the AN/FSQ-7
for SAGE rather than the one that went through the
Cray I.
Be that as it may...
So, IIRC, one can write
A = B * C
in Fortran today and if A, B, and C are arrays having the
same dimensions (or if one of B or C is a scalar) it will
do an element-by-element multiplication, much as would
have been done by APL, without the need for a DO loop.
I remember that back in the 1970s I mentioned to someone
that I thought this sort of thing would be a nice feature to
have, even without vector hardware. The fellow replied that
I was being too slavish in following APL, matrix multiplication
would be better.
Well, I agreed that matrix multiplication was useful in
scientific and engineering computation.
But I felt that this would assign inappropriate semantics
to the construct of an array in Fortran.
Of course, one can add a built-in subroutine for matrix
multiplication.
But more recently, I thought that there might be a better
solution; and now on this newsgroup, I've thought to
present it more widely.
Fortran has a COMPLEX data type.
Why can't it have a MATRIX data type?
So there would be a MATRIX statement, having much the
same syntax as a DIMENSION statement. It could be used
to create one-dimensional vectors and matrices that were
not square.
If A, B, and C are declared as matrices (or one of B or C is
a scalar) then
A = B * C
would perform matrix multiplication as expected... if one of
B or C is a one-dimensional matrix, it would be treated as
either a row or column vector, whichever one would make
sense. (Unless that isn't practical; declaring 1 by n and n by 1
matrices instead to make it explicit is certainly an option as
well.)
Since a matrix is an a x b grid of floating-point numbers
combined into an object with specific mathematical
properties, while it would be possible to create an array
of matrices, it would *not* be possible to create a matrix
the elements of which are arrays.
For much the same reason that you can't make a
complex number the real and imaginary parts of which
are arrays of the same shape.
But, yes, it should be allowed to create a matrix the elements
of which are complex numbers. (Or quaternions, for that
matter!)
Oh, and by the way: yes, it should be possible to take a (4,4)
matrix the elements of which are REAL*16 and EQUIVALENCE
it to a (4,4) array the elements of which are REAL*16 with the
expected results of the same-numbered elements perfectly
corresponding. (This might even be a reason not to deprecate
EQUIVALENCE...)
Is this not a useful thing to include in Fortran?
Maybe someone else has even suggested it already?
John Savard
When was the last time you wrote anything in Fortran ?
I write software in Fortran almost every day. Or C++. Or if I am
lucky, both. I could never use a matrix object package in our software
since the computations are so long and usually dependent upon the
previous computations. We do use some of the Harwell routines, heavily modified.
On 11/2/2021 8:42 PM, gah4 wrote:
I wasn't especially trying to follow it, but I thought I remembered something about standardizing the descriptors, such that one could
do things like this.
"C descriptors" are somewhat standardized, but they're not visible to
Fortran code and the standard permits layout differences. C code can
access the descriptors through macros and structure declarations in ISO_Fortran_binding.h. Some compilers might use C descriptors to
represent pointers in Fortran code, but there's no requirement that this
be done. Even the specific layout of a C descriptor's bounds section is implementation-dependent.
On Thursday, November 4, 2021 at 9:14:21 AM UTC-7, Steve Lionel wrote:
On 11/2/2021 8:42 PM, gah4 wrote:
I wasn't especially trying to follow it, but I thought I remembered something about standardizing the descriptors, such that one could
do things like this.
"C descriptors" are somewhat standardized, but they're not visible to Fortran code and the standard permits layout differences. C code can access the descriptors through macros and structure declarations in ISO_Fortran_binding.h. Some compilers might use C descriptors toSo with some strange mix of Fortran and C, it should be possible to
represent pointers in Fortran code, but there's no requirement that this be done. Even the specific layout of a C descriptor's bounds section is implementation-dependent.
do what was wanted. Maybe not so convenient, though.
"C descriptors" are somewhat standardized, but they're not visible toSo with some strange mix of Fortran and C, it should be possible to
Fortran code and the standard permits layout differences. C code can
access the descriptors through macros and structure declarations in
ISO_Fortran_binding.h. Some compilers might use C descriptors to
represent pointers in Fortran code, but there's no requirement that this
be done. Even the specific layout of a C descriptor's bounds section is
implementation-dependent.
do what was wanted. Maybe not so convenient, though.
Il giorno venerdì 5 novembre 2021 alle 07:56:05 UTC+1 gah4 ha scritto:
On Thursday, November 4, 2021 at 9:14:21 AM UTC-7, Steve Lionel wrote:
On 11/2/2021 8:42 PM, gah4 wrote:So with some strange mix of Fortran and C, it should be possible to
I wasn't especially trying to follow it, but I thought I remembered
something about standardizing the descriptors, such that one could
do things like this.
"C descriptors" are somewhat standardized, but they're not visible to
Fortran code and the standard permits layout differences. C code can
access the descriptors through macros and structure declarations in
ISO_Fortran_binding.h. Some compilers might use C descriptors to
represent pointers in Fortran code, but there's no requirement that this >>> be done. Even the specific layout of a C descriptor's bounds section is
implementation-dependent.
do what was wanted. Maybe not so convenient, though.
I don't think that a manipulation of the descriptor should be allowed except when using pointers.
If I write something like:
B = shallowTranspose(A)
Now B and A are pointing to the same memory and now one have aliasing without the processor really knowing. While one should have something like:
..., target :: A
..., pointer :: B
B => shallowTranspose(A)
Or whatever manipulation of the descriptor one want to allow. Now the standard Fortran rules apply and the processor know that the two arrays may share the same memory.
On 11/5/21 5:51 AM, Edmondo Giovannozzi wrote:
Il giorno venerdì 5 novembre 2021 alle 07:56:05 UTC+1 gah4 ha scritto:
On Thursday, November 4, 2021 at 9:14:21 AM UTC-7, Steve Lionel wrote:
On 11/2/2021 8:42 PM, gah4 wrote:So with some strange mix of Fortran and C, it should be possible to
I wasn't especially trying to follow it, but I thought I remembered
something about standardizing the descriptors, such that one could
do things like this.
"C descriptors" are somewhat standardized, but they're not visible to
Fortran code and the standard permits layout differences. C code can
access the descriptors through macros and structure declarations in
ISO_Fortran_binding.h. Some compilers might use C descriptors to
represent pointers in Fortran code, but there's no requirement that
this
be done. Even the specific layout of a C descriptor's bounds section is >>>> implementation-dependent.
do what was wanted. Maybe not so convenient, though.
I don't think that a manipulation of the descriptor should be allowed
except when using pointers.
If I write something like:
B = shallowTranspose(A)
Now B and A are pointing to the same memory and now one have aliasing
without the processor really knowing. While one should have something
like:
..., target :: A
..., pointer :: B
B => shallowTranspose(A)
Or whatever manipulation of the descriptor one want to allow. Now the
standard Fortran rules apply and the processor know that the two
arrays may share the same memory.
Yes, pointer assignment was exactly what was suggested in the earlier
post, along with ability to use shallow_transpose() in expressions and
as an actual argument to a subprogram. In that latter case, it would be
the programmer's responsibility to avoid aliases with other arguments --
I don't think the compiler could know and check for those in all cases.
I also suggested the possibility of using the function on the left hand
side of expressions.
shallow_transpose(B) = <some expression>
That would be beyond what is currently allowed in fortran,
but it would
match what is allowed in formal mathematical expressions. In terms of low-level operations, that would require the allocation of memory to
evaluate the expression,
association of that memory to the pointer, and
adjusting the pointer metadata to account for the transpose.
That seems
complicated, knowing when that memory can be deallocated by the compiler
and what other things can be done with it (e.g. subsequent pointer assignments, etc.).
So maybe that isn't really practical, despite its
apparent usefulness.
In any case, in the end the shallow_transpose() would be an intrinsic function defined as part of the fortran standard.
The programmer would
not need to fiddle around with any descriptors using C interop or
anything like that.
I see no difference between the situation after:
B = <some expression>
and
shallow_transpose(B) = <some expression>
At least not for what can be done with the memory region
involved. To me it would seem to be more tricky to have
B(i:j, n:m) = <some expression>
where only a slice of the total allocated memory of B is
being used.
On Monday, November 8, 2021 at 2:00:04 PM UTC-8, Jos Bergervoet wrote:.
(snip)
I see no difference between the situation after:Other than it isn't usual to use functions on the left side of an assignment.
B = <some expression>
and
shallow_transpose(B) = <some expression>
PL/I has pseudo-variables, which do look like functions,
and can appear.
on the left side. In the case of a function that is its own inverse, though
I don't see the need.
PL/I has the REAL and IMAG functions to extract the real and imaginary
parts of a complex value, and COMPLEX to make a complex value from
real and imaginary parts.
Then there are the REAL and IMAG pseudo-variables to change the real.
or imaginary part of a complex value, and COMPLEX to extract both.
X=REAL(Z);
Y=IMAG(Z);
can be written as:
COMPLEX(X,Y) = Z;
IMAG(Z) = Y;
to change only the imaginary part. Much more obvious than:
Z = COMPLEX(REAL(Z), Y);
and even:
DO IMAG(Z) = 1 TO 100;
where it is both a function and pseudo-variable.
There is also SUBSTR(), as a function to extract a substring, and pseudo-variable to change a substring in a string.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 159 |
Nodes: | 16 (0 / 16) |
Uptime: | 98:48:35 |
Calls: | 3,209 |
Files: | 10,563 |
Messages: | 3,009,783 |