From 467d03f042fd44d69a9398702ee647b56e1b97ec Mon Sep 17 00:00:00 2001 From: mmatera Date: Fri, 18 Aug 2023 13:15:20 -0300 Subject: [PATCH 01/51] files_io doctest-> pytest --- mathics/builtin/files_io/files.py | 220 ++---------------- mathics/builtin/files_io/filesystem.py | 132 ----------- mathics/builtin/files_io/importexport.py | 133 +---------- test/builtin/files_io/test_files.py | 256 +++++++++++++++++++++ test/builtin/files_io/test_importexport.py | 182 ++++++++++++++- 5 files changed, 460 insertions(+), 463 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 2c3272af4..29c5d21e3 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -204,11 +204,7 @@ class Close(Builtin): Closing a file doesn't delete it from the filesystem >> DeleteFile[file]; - #> Close["abc"] - : abc is not open. - = Close[abc] - - #> Clear[file] + >> Clear[file] """ summary_text = "close a stream" @@ -277,18 +273,6 @@ class FilePrint(Builtin):
prints the raw contents of $file$. - #> exp = Sin[1]; - #> FilePrint[exp] - : File specification Sin[1] is not a string of one or more characters. - = FilePrint[Sin[1]] - - #> FilePrint["somenonexistentpath_h47sdmk^&h4"] - : Cannot open somenonexistentpath_h47sdmk^&h4. - = FilePrint[somenonexistentpath_h47sdmk^&h4] - - #> FilePrint[""] - : File specification is not a string of one or more characters. - = FilePrint[] """ messages = { @@ -394,16 +378,6 @@ class Get(PrefixOperator): ## TODO: Requires EndPackage implemented ## 'Get' can also load packages: ## >> << "VectorAnalysis`" - - #> Get["SomeTypoPackage`"] - : Cannot open SomeTypoPackage`. - = $Failed - - ## Parser Tests - #> Hold[<< ~/some_example/dir/] // FullForm - = Hold[Get["~/some_example/dir/"]] - #> Hold[<<`/.\-_:$*~?] // FullForm - = Hold[Get["`/.\\\\-_:$*~?"]] """ operator = "<<" options = { @@ -528,29 +502,9 @@ class OpenRead(_OpenAction): >> OpenRead["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"] = InputStream[...] - #> Close[%]; + >> Close[%]; S> Close[OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"]]; - - #> OpenRead[] - : OpenRead called with 0 arguments; 1 argument is expected. - = OpenRead[] - - #> OpenRead[y] - : File specification y is not a string of one or more characters. - = OpenRead[y] - - #> OpenRead[""] - : File specification is not a string of one or more characters. - = OpenRead[] - - #> OpenRead["MathicsNonExampleFile"] - : Cannot open MathicsNonExampleFile. - = OpenRead[MathicsNonExampleFile] - - #> OpenRead["ExampleData/EinsteinSzilLetter.txt", BinaryFormat -> True, CharacterEncoding->"UTF8"] - = InputStream[...] - #> Close[%]; """ summary_text = "open a file for reading" @@ -569,11 +523,7 @@ class OpenWrite(_OpenAction): >> OpenWrite[] = OutputStream[...] - #> DeleteFile[Close[%]]; - - #> OpenWrite[BinaryFormat -> True] - = OutputStream[...] - #> DeleteFile[Close[%]]; + >> DeleteFile[Close[%]]; """ summary_text = ( @@ -594,14 +544,8 @@ class OpenAppend(_OpenAction): >> OpenAppend[] = OutputStream[...] - #> DeleteFile[Close[%]]; - - #> appendFile = OpenAppend["MathicsNonExampleFile"] - = OutputStream[MathicsNonExampleFile, ...] + >> DeleteFile[Close[%]]; - #> Close[appendFile] - = MathicsNonExampleFile - #> DeleteFile["MathicsNonExampleFile"] """ mode = "a" @@ -757,17 +701,7 @@ class PutAppend(BinaryOperator): | 265252859812191058636308480000000 | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000 | "string" - #> DeleteFile["factorials"]; - - ## writing to dir - #> x >>> /var/ - : Cannot open /var/. - = x >>> /var/ - - ## writing to read only file - #> x >>> /proc/uptime - : Cannot open /proc/uptime. - = x >>> /proc/uptime + >> DeleteFile["factorials"]; """ operator = ">>>" @@ -842,22 +776,14 @@ class Read(Builtin):
  • Word - ## Malformed InputString - #> Read[InputStream[String], {Word, Number}] - = Read[InputStream[String], {Word, Number}] - - ## Correctly formed InputString but not open - #> Read[InputStream[String, -1], {Word, Number}] - : InputStream[String, -1] is not open. - = Read[InputStream[String, -1], {Word, Number}] ## Reading Strings >> stream = StringToStream["abc123"]; >> Read[stream, String] = abc123 - #> Read[stream, String] + >> Read[stream, String] = EndOfFile - #> Close[stream]; + >> Close[stream]; ## Reading Words >> stream = StringToStream["abc 123"]; @@ -865,60 +791,19 @@ class Read(Builtin): = abc >> Read[stream, Word] = 123 - #> Read[stream, Word] - = EndOfFile - #> Close[stream]; - #> stream = StringToStream[""]; - #> Read[stream, Word] - = EndOfFile - #> Read[stream, Word] + >> Read[stream, Word] = EndOfFile - #> Close[stream]; - + >> Close[stream]; ## Number >> stream = StringToStream["123, 4"]; >> Read[stream, Number] = 123 >> Read[stream, Number] = 4 - #> Read[stream, Number] + >> Read[stream, Number] = EndOfFile - #> Close[stream]; - #> stream = StringToStream["123xyz 321"]; - #> Read[stream, Number] - = 123 - #> Quiet[Read[stream, Number]] - = $Failed - - ## Real - #> stream = StringToStream["123, 4abc"]; - #> Read[stream, Real] - = 123. - #> Read[stream, Real] - = 4. - #> Quiet[Read[stream, Number]] - = $Failed - - #> Close[stream]; - #> stream = StringToStream["1.523E-19"]; Read[stream, Real] - = 1.523×10^-19 - #> Close[stream]; - #> stream = StringToStream["-1.523e19"]; Read[stream, Real] - = -1.523×10^19 - #> Close[stream]; - #> stream = StringToStream["3*^10"]; Read[stream, Real] - = 3.×10^10 - #> Close[stream]; - #> stream = StringToStream["3.*^10"]; Read[stream, Real] - = 3.×10^10 - #> Close[stream]; - - ## Expression - #> stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression] - = x + y Sin[z] - #> Close[stream]; - ## #> stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]] - ## = $Failed + >> Close[stream]; + ## HoldExpression: >> stream = StringToStream["2+2\\n2+3"]; @@ -944,21 +829,9 @@ class Read(Builtin): >> stream = StringToStream["123 abc"]; >> Read[stream, {Number, Word}] = {123, abc} - #> Read[stream, {Number, Word}] + >> Read[stream, {Number, Word}] = EndOfFile - #> lose[stream]; - - #> stream = StringToStream["123 abc"]; - #> Quiet[Read[stream, {Word, Number}]] - = $Failed - #> Close[stream]; - - #> stream = StringToStream["123 123"]; Read[stream, {Real, Number}] - = {123., 123} - #> Close[stream]; - - #> Quiet[Read[stream, {Real}]] - = Read[InputStream[String, ...], {Real}] + >> Close[stream]; Multiple lines: >> stream = StringToStream["\\"Tengo una\\nvaca lechera.\\""]; Read[stream] @@ -1232,15 +1105,7 @@ class ReadList(Read): = {abc123} >> InputForm[%] = {"abc123"} - - #> ReadList[stream, "Invalid"] - : Invalid is not a valid format specification. - = ReadList[..., Invalid] - #> Close[stream]; - - - #> ReadList[StringToStream["a 1 b 2"], {Word, Number}, 1] - = {{a, 1}} + >> Close[stream]; """ # TODO @@ -1398,10 +1263,6 @@ class SetStreamPosition(Builtin): >> Read[stream, Word] = is - #> SetStreamPosition[stream, -5] - : Invalid I/O Seek. - = 10 - >> SetStreamPosition[stream, Infinity] = 16 """ @@ -1482,7 +1343,7 @@ class Skip(Read): >> Skip[stream, Word] >> Read[stream, Word] = c - #> Close[stream]; + >> Close[stream]; >> stream = StringToStream["a b c d"]; >> Read[stream, Word] @@ -1490,9 +1351,9 @@ class Skip(Read): >> Skip[stream, Word, 2] >> Read[stream, Word] = d - #> Skip[stream, Word] + >> Skip[stream, Word] = EndOfFile - #> Close[stream]; + >> Close[stream]; """ messages = { @@ -1649,14 +1510,7 @@ class StringToStream(Builtin): >> strm = StringToStream["abc 123"] = InputStream[String, ...] - #> Read[strm, Word] - = abc - - #> Read[strm, Number] - = 123 - - #> Close[strm] - = String + >> Close[strm]; """ summary_text = "open an input stream for reading from a string" @@ -1685,14 +1539,6 @@ class Streams(Builtin): >> Streams["stdout"] = ... - - #> OpenWrite[] - = ... - #> Streams[%[[1]]] - = {OutputStream[...]} - - #> Streams["some_nonexistent_name"] - = {} """ summary_text = "list currently open streams" @@ -1768,7 +1614,7 @@ class Write(Builtin): >> stream = OpenRead[%]; >> ReadList[stream] = {10 x + 15 y ^ 2, 3 Sin[z]} - #> DeleteFile[Close[stream]]; + >> DeleteFile[Close[stream]]; """ summary_text = "write a sequence of expressions to a stream, ending the output with a newline (line feed)" @@ -1817,7 +1663,7 @@ class WriteString(Builtin): >> FilePrint[%] | This is a test 1This is also a test 2 - #> DeleteFile[pathname]; + >> DeleteFile[pathname]; >> stream = OpenWrite[]; >> WriteString[stream, "This is a test 1", "This is also a test 2"] >> pathname = Close[stream] @@ -1825,29 +1671,7 @@ class WriteString(Builtin): >> FilePrint[%] | This is a test 1This is also a test 2 - #> DeleteFile[pathname]; - #> stream = OpenWrite[]; - #> WriteString[stream, 100, 1 + x + y, Sin[x + y]] - #> pathname = Close[stream] - = ... - #> FilePrint[%] - | 1001 + x + ySin[x + y] - - #> DeleteFile[pathname]; - #> stream = OpenWrite[]; - #> WriteString[stream] - #> pathame = Close[stream] - = ... - #> FilePrint[%] - - #> WriteString[%%, abc] - #> Streams[%%%][[1]] - = ... - #> pathname = Close[%]; - #> FilePrint[%] - | abc - #> DeleteFile[pathname]; - #> Clear[pathname]; + >> DeleteFile[pathname]; If stream is the string "stdout" or "stderr", writes to the system standard output/ standard error channel: diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index edb9fdb3e..f0faf505e 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -53,9 +53,6 @@ class AbsoluteFileName(Builtin): >> AbsoluteFileName["ExampleData/sunflowers.jpg"] = ... - #> AbsoluteFileName["Some/NonExistant/Path.ext"] - : File not found during AbsoluteFileName[Some/NonExistant/Path.ext]. - = $Failed """ messages = { @@ -363,23 +360,6 @@ class DirectoryName(Builtin): >> DirectoryName["a/b/c", 2] = a - - #> DirectoryName["a/b/c", 3] // InputForm - = "" - #> DirectoryName[""] // InputForm - = "" - - #> DirectoryName["a/b/c", x] - : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x]. - = DirectoryName[a/b/c, x] - - #> DirectoryName["a/b/c", -1] - : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1]. - = DirectoryName[a/b/c, -1] - - #> DirectoryName[x] - : String expected at position 1 in DirectoryName[x]. - = DirectoryName[x] """ messages = { @@ -508,12 +488,6 @@ class FileBaseName(Builtin): >> FileBaseName["file.tar.gz"] = file.tar - - #> FileBaseName["file."] - = file - - #> FileBaseName["file"] - = file """ options = { @@ -627,11 +601,6 @@ class FileExtension(Builtin): >> FileExtension["file.tar.gz"] = gz - - #> FileExtension["file."] - = #<--# - #> FileExtension["file"] - = #<--# """ options = { @@ -660,9 +629,6 @@ class FileInformation(Builtin): >> FileInformation["ExampleData/sunflowers.jpg"] = {File -> ..., FileType -> File, ByteCount -> 142286, Date -> ...} - - #> FileInformation["ExampleData/missing_file.jpg"] - = {} """ rules = { @@ -688,9 +654,6 @@ class FindFile(Builtin): >> FindFile["VectorAnalysis`VectorAnalysis`"] = ... - - #> FindFile["SomeTypoPackage`"] - = $Failed """ messages = { @@ -938,97 +901,6 @@ class Needs(Builtin): >> Needs["VectorAnalysis`"] - #> Needs["VectorAnalysis`"] - - #> Needs["SomeFakePackageOrTypo`"] - : Cannot open SomeFakePackageOrTypo`. - : Context SomeFakePackageOrTypo` was not created when Needs was evaluated. - = $Failed - - #> Needs["VectorAnalysis"] - : Invalid context specified at position 1 in Needs[VectorAnalysis]. A context must consist of valid symbol names separated by and ending with `. - = Needs[VectorAnalysis] - - ## --- VectorAnalysis --- - - #> Needs["VectorAnalysis`"] - - #> DotProduct[{1,2,3}, {4,5,6}] - = 32 - #> DotProduct[{-1.4, 0.6, 0.2}, {0.1, 0.6, 1.7}] - = 0.56 - - #> CrossProduct[{1,2,3}, {4,5,6}] - = {-3, 6, -3} - #> CrossProduct[{-1.4, 0.6, 0.2}, {0.1, 0.6, 1.7}] - = {0.9, 2.4, -0.9} - - #> ScalarTripleProduct[{-2,3,1},{0,4,0},{-1,3,3}] - = -20 - #> ScalarTripleProduct[{-1.4,0.6,0.2}, {0.1,0.6,1.7}, {0.7,-1.5,-0.2}] - = -2.79 - - #> CoordinatesToCartesian[{2, Pi, 3}, Spherical] - = {0, 0, -2} - #> CoordinatesFromCartesian[%, Spherical] - = {2, Pi, 0} - #> CoordinatesToCartesian[{2, Pi, 3}, Cylindrical] - = {-2, 0, 3} - #> CoordinatesFromCartesian[%, Cylindrical] - = {2, Pi, 3} - ## Needs Sin/Cos exact value (PR #100) for these tests to pass - ## #> CoordinatesToCartesian[{2, Pi / 4, Pi / 3}, Spherical] - ## = {Sqrt[2] / 2, Sqrt[6] / 2, Sqrt[2]} - ## #> CoordinatesFromCartesian[%, Spherical] - ## = {2, Pi / 4, Pi / 3} - ## #> CoordinatesToCartesian[{2, Pi / 4, -1}, Cylindrical] - ## = {Sqrt[2], Sqrt[2], -1} - ## #> CoordinatesFromCartesian[%, Cylindrical] - ## = {2, Pi / 4, -1} - #> CoordinatesToCartesian[{0.27, 0.51, 0.92}, Cylindrical] - = {0.235641, 0.131808, 0.92} - #> CoordinatesToCartesian[{0.27, 0.51, 0.92}, Spherical] - = {0.0798519, 0.104867, 0.235641} - - #> Coordinates[] - = {Xx, Yy, Zz} - #> Coordinates[Spherical] - = {Rr, Ttheta, Pphi} - #> SetCoordinates[Cylindrical] - = Cylindrical[Rr, Ttheta, Zz] - #> Coordinates[] - = {Rr, Ttheta, Zz} - #> CoordinateSystem - = Cylindrical - #> Parameters[] - = {} - #> CoordinateRanges[] - ## = {0 <= Rr < Infinity, -Pi < Ttheta <= Pi, -Infinity < Zz < Infinity} - = {0 <= Rr && Rr < Infinity, -Pi < Ttheta && Ttheta <= Pi, -Infinity < Zz < Infinity} - #> CoordinateRanges[Cartesian] - = {-Infinity < Xx < Infinity, -Infinity < Yy < Infinity, -Infinity < Zz < Infinity} - #> ScaleFactors[Cartesian] - = {1, 1, 1} - #> ScaleFactors[Spherical] - = {1, Rr, Rr Sin[Ttheta]} - #> ScaleFactors[Cylindrical] - = {1, Rr, 1} - #> ScaleFactors[{2, 1, 3}, Cylindrical] - = {1, 2, 1} - #> JacobianDeterminant[Cartesian] - = 1 - #> JacobianDeterminant[Spherical] - = Rr ^ 2 Sin[Ttheta] - #> JacobianDeterminant[Cylindrical] - = Rr - #> JacobianDeterminant[{2, 1, 3}, Cylindrical] - = 2 - #> JacobianMatrix[Cartesian] - = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} - #> JacobianMatrix[Spherical] - = {{Cos[Pphi] Sin[Ttheta], Rr Cos[Pphi] Cos[Ttheta], -Rr Sin[Pphi] Sin[Ttheta]}, {Sin[Pphi] Sin[Ttheta], Rr Cos[Ttheta] Sin[Pphi], Rr Cos[Pphi] Sin[Ttheta]}, {Cos[Ttheta], -Rr Sin[Ttheta], 0}} - #> JacobianMatrix[Cylindrical] - = {{Cos[Ttheta], -Rr Sin[Ttheta], 0}, {Sin[Ttheta], Rr Cos[Ttheta], 0}, {0, 0, 1}} """ messages = { @@ -1223,10 +1095,6 @@ class SetDirectory(Builtin): S> SetDirectory[] = ... - - #> SetDirectory["MathicsNonExample"] - : Cannot set current directory to MathicsNonExample. - = $Failed """ messages = { diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index 42a147eb9..01e2b8ac0 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -1197,7 +1197,7 @@ class RegisterExport(Builtin): >> FilePrint["sample.txt"] | Encode this string! - #> DeleteFile["sample.txt"] + >> DeleteFile["sample.txt"] Very basic encrypted text exporter: >> ExampleExporter2[filename_, data_, opts___] := Module[{strm = OpenWrite[filename], char}, (* TODO: Check data *) char = FromCharacterCode[Mod[ToCharacterCode[data] - 84, 26] + 97]; WriteString[strm, char]; Close[strm]] @@ -1209,7 +1209,7 @@ class RegisterExport(Builtin): >> FilePrint["sample.txt"] | rapbqrguvffgevat - #> DeleteFile["sample.txt"] + >> DeleteFile["sample.txt"] """ summary_text = "register an exporter for a file format" @@ -1247,13 +1247,6 @@ class URLFetch(Builtin):
    'URLFetch[$URL$]'
    Returns the content of $URL$ as a string. - - - #> Quiet[URLFetch["https:////", {}]] - = $Failed - - ##> Quiet[URLFetch["https://www.example.com", {}]] - # = ... """ summary_text = "fetch data from a URL" @@ -1340,38 +1333,16 @@ class Import(Builtin):
    imports from a URL. - #> Import["ExampleData/ExampleData.tx"] - : File not found during Import. - = $Failed - #> Import[x] - : First argument x is not a valid file, directory, or URL specification. - = $Failed - - ## CSV - #> Import["ExampleData/numberdata.csv", "Elements"] - = {Data, Grid} - #> Import["ExampleData/numberdata.csv", "Data"] - = {{0.88, 0.60, 0.94}, {0.76, 0.19, 0.51}, {0.97, 0.04, 0.26}, {0.33, 0.74, 0.79}, {0.42, 0.64, 0.56}} - #> Import["ExampleData/numberdata.csv"] - = {{0.88, 0.60, 0.94}, {0.76, 0.19, 0.51}, {0.97, 0.04, 0.26}, {0.33, 0.74, 0.79}, {0.42, 0.64, 0.56}} - #> Import["ExampleData/numberdata.csv", "FieldSeparators" -> "."] - = {{0, 88,0, 60,0, 94}, {0, 76,0, 19,0, 51}, {0, 97,0, 04,0, 26}, {0, 33,0, 74,0, 79}, {0, 42,0, 64,0, 56}} ## Text >> Import["ExampleData/ExampleData.txt", "Elements"] = {Data, Lines, Plaintext, String, Words} >> Import["ExampleData/ExampleData.txt", "Lines"] = ... - #> Import["ExampleData/Middlemarch.txt"]; - : An invalid unicode sequence was encountered and ignored. ## JSON >> Import["ExampleData/colors.json"] = {colorsArray -> {{colorName -> black, rgbValue -> (0, 0, 0), hexValue -> #000000}, {colorName -> red, rgbValue -> (255, 0, 0), hexValue -> #FF0000}, {colorName -> green, rgbValue -> (0, 255, 0), hexValue -> #00FF00}, {colorName -> blue, rgbValue -> (0, 0, 255), hexValue -> #0000FF}, {colorName -> yellow, rgbValue -> (255, 255, 0), hexValue -> #FFFF00}, {colorName -> cyan, rgbValue -> (0, 255, 255), hexValue -> #00FFFF}, {colorName -> magenta, rgbValue -> (255, 0, 255), hexValue -> #FF00FF}, {colorName -> white, rgbValue -> (255, 255, 255), hexValue -> #FFFFFF}}} - - ## XML - #> Import["ExampleData/InventionNo1.xml", "Tags"] - = {accidental, alter, arpeggiate, ..., words} """ messages = { @@ -1632,26 +1603,6 @@ class ImportString(Import):
    attempts to determine the format of the string from its content. - - #> ImportString[x] - : First argument x is not a string. - = $Failed - - ## CSV - #> datastring = "0.88, 0.60, 0.94\\n.076, 0.19, .51\\n0.97, 0.04, .26"; - #> ImportString[datastring, "Elements"] - = {Data, Lines, Plaintext, String, Words} - #> ImportString[datastring, {"CSV","Elements"}] - = {Data, Grid} - #> ImportString[datastring, {"CSV", "Data"}] - = {{0.88, 0.60, 0.94}, {.076, 0.19, .51}, {0.97, 0.04, .26}} - #> ImportString[datastring] - = 0.88, 0.60, 0.94 - . .076, 0.19, .51 - . 0.97, 0.04, .26 - #> ImportString[datastring, "CSV","FieldSeparators" -> "."] - = {{0, 88, 0, 60, 0, 94}, {076, 0, 19, , 51}, {0, 97, 0, 04, , 26}} - ## Text >> str = "Hello!\\n This is a testing text\\n"; >> ImportString[str, "Elements"] @@ -1736,49 +1687,6 @@ class Export(Builtin):
    'Export["$file$", $exprs$, $elems$]'
    exports $exprs$ to a file as elements specified by $elems$. - - ## Invalid Filename - #> Export["abc.", 1+2] - : Cannot infer format of file abc.. - = $Failed - #> Export[".ext", 1+2] - : Cannot infer format of file .ext. - = $Failed - #> Export[x, 1+2] - : First argument x is not a valid file specification. - = $Failed - - ## Explicit Format - #> Export["abc.txt", 1+x, "JPF"] - : {JPF} is not a valid set of export elements for the Text format. - = $Failed - #> Export["abc.txt", 1+x, {"JPF"}] - : {JPF} is not a valid set of export elements for the Text format. - = $Failed - - ## Empty elems - #> Export["123.txt", 1+x, {}] - = 123.txt - #> Export["123.jcp", 1+x, {}] - : Cannot infer format of file 123.jcp. - = $Failed - - ## Compression - ## #> Export["abc.txt", 1+x, "ZIP"] (* MMA Bug - Export::type *) - ## : {ZIP} is not a valid set of export elements for the Text format. - ## = $Failed - ## #> Export["abc.txt", 1+x, "BZIP"] (* MMA Bug - General::stop *) - ## : {BZIP} is not a valid set of export elements for the Text format. - ## = $Failed - ## #> Export["abc.txt", 1+x, {"BZIP", "ZIP", "Text"}] - ## = abc.txt - ## #> Export["abc.txt", 1+x, {"GZIP", "Text"}] - ## = abc.txt - ## #> Export["abc.txt", 1+x, {"BZIP2", "Text"}] - ## = abc.txt - - ## FORMATS - """ messages = { @@ -2146,43 +2054,6 @@ class FileFormat(Builtin): >> FileFormat["ExampleData/hedy.tif"] = TIFF - - ## ASCII text - #> FileFormat["ExampleData/BloodToilTearsSweat.txt"] - = Text - #> FileFormat["ExampleData/MadTeaParty.gif"] - = GIF - #> FileFormat["ExampleData/moon.tif"] - = TIFF - - #> FileFormat["ExampleData/numberdata.csv"] - = CSV - - #> FileFormat["ExampleData/EinsteinSzilLetter.txt"] - = Text - - #> FileFormat["ExampleData/BloodToilTearsSweat.txt"] - = Text - - ## Doesn't work on Microsoft Windows - ## S> FileFormat["ExampleData/benzene.xyz"] - ## = XYZ - - #> FileFormat["ExampleData/colors.json"] - = JSON - - #> FileFormat["ExampleData/some-typo.extension"] - : File not found during FileFormat[ExampleData/some-typo.extension]. - = $Failed - - #> FileFormat["ExampleData/Testosterone.svg"] - = SVG - - #> FileFormat["ExampleData/colors.json"] - = JSON - - #> FileFormat["ExampleData/InventionNo1.xml"] - = XML """ summary_text = "determine the file format of a file" diff --git a/test/builtin/files_io/test_files.py b/test/builtin/files_io/test_files.py index 3e0a2bf6c..adb0dcc52 100644 --- a/test/builtin/files_io/test_files.py +++ b/test/builtin/files_io/test_files.py @@ -80,6 +80,262 @@ def test_close(): ), f"temporary filename {temp_filename} should not appear" +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ('Close["abc"]', ("abc is not open.",), "Close[abc]", None), + ( + "exp = Sin[1]; FilePrint[exp]", + ("File specification Sin[1] is not a string of one or more characters.",), + "FilePrint[Sin[1]]", + None, + ), + ( + 'FilePrint["somenonexistentpath_h47sdmk^&h4"]', + ("Cannot open somenonexistentpath_h47sdmk^&h4.",), + "FilePrint[somenonexistentpath_h47sdmk^&h4]", + None, + ), + ( + 'FilePrint[""]', + ("File specification is not a string of one or more characters.",), + "FilePrint[]", + None, + ), + ( + 'Get["SomeTypoPackage`"]', + ("Cannot open SomeTypoPackage`.",), + "$Failed", + None, + ), + ## Parser Tests + ( + "Hold[<< ~/some_example/dir/] // FullForm", + None, + 'Hold[Get["~/some_example/dir/"]]', + None, + ), + ( + r"Hold[<<`/.\-_:$*~?] // FullForm", + None, + r'Hold[Get["`/.\\\\-_:$*~?"]]', + None, + ), + ( + "OpenRead[]", + ("OpenRead called with 0 arguments; 1 argument is expected.",), + "OpenRead[]", + None, + ), + ( + "OpenRead[y]", + ("File specification y is not a string of one or more characters.",), + "OpenRead[y]", + None, + ), + ( + 'OpenRead[""]', + ("File specification is not a string of one or more characters.",), + "OpenRead[]", + None, + ), + ( + 'OpenRead["MathicsNonExampleFile"]', + ("Cannot open MathicsNonExampleFile.",), + "OpenRead[MathicsNonExampleFile]", + None, + ), + ( + 'fd=OpenRead["ExampleData/EinsteinSzilLetter.txt", BinaryFormat -> True, CharacterEncoding->"UTF8"]//Head', + None, + "InputStream", + None, + ), + ( + "Close[fd]; fd=.;fd=OpenWrite[BinaryFormat -> True]//Head", + None, + "OutputStream", + None, + ), + ( + 'DeleteFile[Close[fd]];fd=.;appendFile = OpenAppend["MathicsNonExampleFile"]//{#1[[0]],#1[[1]]}&', + None, + "{OutputStream, MathicsNonExampleFile}", + None, + ), + ( + "Close[appendFile]", + None, + "Close[{OutputStream, MathicsNonExampleFile}]", + None, + ), + ('DeleteFile["MathicsNonExampleFile"]', None, "Null", None), + ## writing to dir + ("x >>> /var/", ("Cannot open /var/.",), "x >>> /var/", None), + ## writing to read only file + ( + "x >>> /proc/uptime", + ("Cannot open /proc/uptime.",), + "x >>> /proc/uptime", + None, + ), + ## Malformed InputString + ( + "Read[InputStream[String], {Word, Number}]", + None, + "Read[InputStream[String], {Word, Number}]", + None, + ), + ## Correctly formed InputString but not open + ( + "Read[InputStream[String, -1], {Word, Number}]", + ("InputStream[String, -1] is not open.",), + "Read[InputStream[String, -1], {Word, Number}]", + None, + ), + ('stream = StringToStream[""];Read[stream, Word]', None, "EndOfFile", None), + ("Read[stream, Word]", None, "EndOfFile", None), + ("Close[stream];", None, "Null", None), + ( + 'stream = StringToStream["123xyz 321"]; Read[stream, Number]', + None, + "123", + None, + ), + ("Quiet[Read[stream, Number]]", None, "$Failed", None), + ## Real + ('stream = StringToStream["123, 4abc"];Read[stream, Real]', None, "123.", None), + ("Read[stream, Real]", None, "4.", None), + ("Quiet[Read[stream, Number]]", None, "$Failed", None), + ("Close[stream];", None, "Null", None), + ( + 'stream = StringToStream["1.523E-19"]; Read[stream, Real]', + None, + "1.523×10^-19", + None, + ), + ("Close[stream];", None, "Null", None), + ( + 'stream = StringToStream["-1.523e19"]; Read[stream, Real]', + None, + "-1.523×10^19", + None, + ), + ("Close[stream];", None, "Null", None), + ( + 'stream = StringToStream["3*^10"]; Read[stream, Real]', + None, + "3.×10^10", + None, + ), + ("Close[stream];", None, "Null", None), + ( + 'stream = StringToStream["3.*^10"]; Read[stream, Real]', + None, + "3.×10^10", + None, + ), + ("Close[stream];", None, "Null", None), + ## Expression + ( + 'stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression]', + None, + "x + y Sin[z]", + None, + ), + ("Close[stream];", None, "Null", None), + ## ('stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]]', None,'$Failed', None), + ( + 'stream = StringToStream["123 abc"]; Quiet[Read[stream, {Word, Number}]]', + None, + "$Failed", + None, + ), + ("Close[stream];", None, "Null", None), + ( + 'stream = StringToStream["123 123"]; Read[stream, {Real, Number}]', + None, + "{123., 123}", + None, + ), + ("Close[stream];", None, "Null", None), + ( + "Quiet[Read[stream, {Real}]]//{#1[[0]],#1[[1]][[0]],#1[[1]][[1]],#1[[2]]}&", + None, + "{Read, InputStream, String, {Real}}", + None, + ), + ( + r'stream = StringToStream["\"abc123\""];ReadList[stream, "Invalid"]//{#1[[0]],#1[[2]]}&', + ("Invalid is not a valid format specification.",), + "{ReadList, Invalid}", + None, + ), + ("Close[stream];", None, "Null", None), + ( + 'ReadList[StringToStream["a 1 b 2"], {Word, Number}, 1]', + None, + "{{a, 1}}", + None, + ), + ('stream = StringToStream["Mathics is cool!"];', None, "Null", None), + ("SetStreamPosition[stream, -5]", ("Invalid I/O Seek.",), "0", None), + ( + '(strm = StringToStream["abc 123"])//{#1[[0]],#1[[1]]}&', + None, + "{InputStream, String}", + None, + ), + ("Read[strm, Word]", None, "abc", None), + ("Read[strm, Number]", None, "123", None), + ("Close[strm]", None, "String", None), + ("(low=OpenWrite[])//Head", None, "OutputStream", None), + ( + "Streams[low[[1]]]//{#1[[0]],#1[[1]][[0]]}&", + None, + "{List, OutputStream}", + None, + ), + ('Streams["some_nonexistent_name"]', None, "{}", None), + ( + "stream = OpenWrite[]; WriteString[stream, 100, 1 + x + y, Sin[x + y]]", + None, + "Null", + None, + ), + ("(pathname = Close[stream])//Head", None, "String", None), + ("FilePrint[pathname]", ("1001 + x + ySin[x + y]",), "Null", None), + ("DeleteFile[pathname];", None, "Null", None), + ( + "stream = OpenWrite[];WriteString[stream];(pathname = Close[stream])//Head", + None, + "String", + None, + ), + ("FilePrint[pathname]", None, "Null", None), + ( + "WriteString[pathname, abc];(laststrm=Streams[pathname][[1]])//Head", + None, + "OutputStream", + None, + ), + ("Close[laststrm];FilePrint[pathname]", ("abc",), "Null", None), + ("DeleteFile[pathname];Clear[pathname];", None, "Null", None), + ], +) +def test_private_doctests_files(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + # I do not know what this is it supposed to test with this... # def test_Inputget_and_put(): # stream = Expression('Plus', Symbol('x'), Integer(2)) diff --git a/test/builtin/files_io/test_importexport.py b/test/builtin/files_io/test_importexport.py index c85d8f59c..f51c38dfe 100644 --- a/test/builtin/files_io/test_importexport.py +++ b/test/builtin/files_io/test_importexport.py @@ -3,13 +3,12 @@ import os.path as osp import sys import tempfile +from test.helper import check_evaluation, evaluate, session import pytest from mathics.builtin.atomic.strings import to_python_encoding -from ...helper import session - # def test_import(): # eaccent = "\xe9" # for str_expr, str_expected, message in ( @@ -82,6 +81,185 @@ def test_export(): assert data.endswith("") +""" + + ## Compression + ## #> Export["abc.txt", 1+x, "ZIP"] (* MMA Bug - Export::type *) + ## : {ZIP} is not a valid set of export elements for the Text format. + ## = $Failed + ## #> Export["abc.txt", 1+x, "BZIP"] (* MMA Bug - General::stop *) + ## : {BZIP} is not a valid set of export elements for the Text format. + ## = $Failed + ## #> Export["abc.txt", 1+x, {"BZIP", "ZIP", "Text"}] + ## = abc.txt + ## #> Export["abc.txt", 1+x, {"GZIP", "Text"}] + ## = abc.txt + ## #> Export["abc.txt", 1+x, {"BZIP2", "Text"}] + ## = abc.txt + + ## Doesn't work on Microsoft Windows + ## S> FileFormat["ExampleData/benzene.xyz"] + ## = XYZ + +""" + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + (r'Quiet[URLFetch["https://", {}]]', None, "$Failed", None), + # (r'Quiet[URLFetch["https://www.example.com", {}]]', None, + # "...", None), + ( + 'Import["ExampleData/ExampleData.tx"]', + ("File not found during Import.",), + "$Failed", + None, + ), + ( + "Import[x]", + ("First argument x is not a valid file, directory, or URL specification.",), + "$Failed", + None, + ), + ## CSV + ( + 'Import["ExampleData/numberdata.csv", "Elements"]', + None, + "{Data, Grid}", + None, + ), + ( + 'Import["ExampleData/numberdata.csv", "Data"]', + None, + "{{0.88, 0.60, 0.94}, {0.76, 0.19, 0.51}, {0.97, 0.04, 0.26}, {0.33, 0.74, 0.79}, {0.42, 0.64, 0.56}}", + None, + ), + ( + 'Import["ExampleData/numberdata.csv"]', + None, + "{{0.88, 0.60, 0.94}, {0.76, 0.19, 0.51}, {0.97, 0.04, 0.26}, {0.33, 0.74, 0.79}, {0.42, 0.64, 0.56}}", + None, + ), + ( + 'Import["ExampleData/numberdata.csv", "FieldSeparators" -> "."]', + None, + "{{0, 88,0, 60,0, 94}, {0, 76,0, 19,0, 51}, {0, 97,0, 04,0, 26}, {0, 33,0, 74,0, 79}, {0, 42,0, 64,0, 56}}", + None, + ), + ( + 'Import["ExampleData/Middlemarch.txt"];', + ("An invalid unicode sequence was encountered and ignored.",), + "Null", + None, + ), + ## XML + ( + 'MatchQ[Import["ExampleData/InventionNo1.xml", "Tags"],{__String}]', + None, + "True", + None, + ), + ("ImportString[x]", ("First argument x is not a string.",), "$Failed", None), + ## CSV + ( + 'datastring = "0.88, 0.60, 0.94\\n.076, 0.19, .51\\n0.97, 0.04, .26";ImportString[datastring, "Elements"]', + None, + "{Data, Lines, Plaintext, String, Words}", + None, + ), + ('ImportString[datastring, {"CSV","Elements"}]', None, "{Data, Grid}", None), + ( + 'ImportString[datastring, {"CSV", "Data"}]', + None, + "{{0.88, 0.60, 0.94}, {.076, 0.19, .51}, {0.97, 0.04, .26}}", + None, + ), + ( + "ImportString[datastring]", + None, + "0.88, 0.60, 0.94\n.076, 0.19, .51\n0.97, 0.04, .26", + None, + ), + ( + 'ImportString[datastring, "CSV","FieldSeparators" -> "."]', + None, + "{{0, 88, 0, 60, 0, 94}, {076, 0, 19, , 51}, {0, 97, 0, 04, , 26}}", + None, + ), + ## Invalid Filename + ( + 'Export["abc.", 1+2]', + ("Cannot infer format of file abc..",), + "$Failed", + None, + ), + ( + 'Export[".ext", 1+2]', + ("Cannot infer format of file .ext.",), + "$Failed", + None, + ), + ( + "Export[x, 1+2]", + ("First argument x is not a valid file specification.",), + "$Failed", + None, + ), + ## Explicit Format + ( + 'Export["abc.txt", 1+x, "JPF"]', + ("{JPF} is not a valid set of export elements for the Text format.",), + "$Failed", + None, + ), + ( + 'Export["abc.txt", 1+x, {"JPF"}]', + ("{JPF} is not a valid set of export elements for the Text format.",), + "$Failed", + None, + ), + ## Empty elems + ('Export["123.txt", 1+x, {}]', None, "123.txt", None), + ( + 'Export["123.jcp", 1+x, {}]', + ("Cannot infer format of file 123.jcp.",), + "$Failed", + None, + ), + ## FORMATS + ## ASCII text + ('FileFormat["ExampleData/BloodToilTearsSweat.txt"]', None, "Text", None), + ('FileFormat["ExampleData/MadTeaParty.gif"]', None, "GIF", None), + ('FileFormat["ExampleData/moon.tif"]', None, "TIFF", None), + ('FileFormat["ExampleData/numberdata.csv"]', None, "CSV", None), + ('FileFormat["ExampleData/EinsteinSzilLetter.txt"]', None, "Text", None), + ('FileFormat["ExampleData/BloodToilTearsSweat.txt"]', None, "Text", None), + ('FileFormat["ExampleData/colors.json"]', None, "JSON", None), + ( + 'FileFormat["ExampleData/some-typo.extension"]', + ("File not found during FileFormat[ExampleData/some-typo.extension].",), + "$Failed", + None, + ), + ('FileFormat["ExampleData/Testosterone.svg"]', None, "SVG", None), + ('FileFormat["ExampleData/colors.json"]', None, "JSON", None), + ('FileFormat["ExampleData/InventionNo1.xml"]', None, "XML", None), + ], +) +def test_private_doctests_importexport(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + # TODO: # mmatera: please put in pytest conditionally # >> System`Convert`B64Dump`B64Encode["∫ f  x"] From 1b082cacd64078a8e6ce3191dd533e79d03ac14d Mon Sep 17 00:00:00 2001 From: mmatera Date: Fri, 18 Aug 2023 22:38:21 -0300 Subject: [PATCH 02/51] add test_filesystem --- test/builtin/files_io/test_filesystem.py | 86 ++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/builtin/files_io/test_filesystem.py diff --git a/test/builtin/files_io/test_filesystem.py b/test/builtin/files_io/test_filesystem.py new file mode 100644 index 000000000..32e2c5b82 --- /dev/null +++ b/test/builtin/files_io/test_filesystem.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from builtins/files_io/filesystem.py +""" +import os.path as osp +import sys +from test.helper import check_evaluation, evaluate + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + 'AbsoluteFileName["Some/NonExistant/Path.ext"]', + ("File not found during AbsoluteFileName[Some/NonExistant/Path.ext].",), + "$Failed", + None, + ), + ('DirectoryName["a/b/c", 3] // InputForm', None, '""', None), + ('DirectoryName[""] // InputForm', None, '""', None), + ( + 'DirectoryName["a/b/c", x]', + ( + "Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x].", + ), + "DirectoryName[a/b/c, x]", + None, + ), + ( + 'DirectoryName["a/b/c", -1]', + ( + "Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1].", + ), + "DirectoryName[a/b/c, -1]", + None, + ), + ( + "DirectoryName[x]", + ("String expected at position 1 in DirectoryName[x].",), + "DirectoryName[x]", + None, + ), + ('FileBaseName["file."]', None, "file", None), + ('FileBaseName["file"]', None, "file", None), + ('FileExtension["file."]', None, "", None), + ('FileExtension["file"]', None, "", None), + ('FileInformation["ExampleData/missing_file.jpg"]', None, "{}", None), + ('FindFile["SomeTypoPackage`"]', None, "$Failed", None), + ( + 'SetDirectory["MathicsNonExample"]', + ("Cannot set current directory to MathicsNonExample.",), + "$Failed", + None, + ), + ( + 'Needs["SomeFakePackageOrTypo`"]', + ( + "Cannot open SomeFakePackageOrTypo`.", + "Context SomeFakePackageOrTypo` was not created when Needs was evaluated.", + ), + "$Failed", + None, + ), + ( + 'Needs["VectorAnalysis"]', + ( + "Invalid context specified at position 1 in Needs[VectorAnalysis]. A context must consist of valid symbol names separated by and ending with `.", + ), + "Needs[VectorAnalysis]", + None, + ), + ], +) +def test_private_doctests_filesystem(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From 513847e2fc8d23f6b6bac8c4d270d62b47553402 Mon Sep 17 00:00:00 2001 From: mmatera Date: Fri, 18 Aug 2023 22:39:13 -0300 Subject: [PATCH 03/51] add test vectoranalysis --- test/package/test_vectoranalysis.py | 122 ++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 test/package/test_vectoranalysis.py diff --git a/test/package/test_vectoranalysis.py b/test/package/test_vectoranalysis.py new file mode 100644 index 000000000..c4648b481 --- /dev/null +++ b/test/package/test_vectoranalysis.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from packages/VectorAnalysis +""" +import os.path as osp +import sys +from test.helper import check_evaluation, evaluate + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + (None, None, None, None), + ('Needs["VectorAnalysis`"];', None, "Null", None), + ("DotProduct[{1,2,3}, {4,5,6}]", None, "32", None), + ("DotProduct[{-1.4, 0.6, 0.2}, {0.1, 0.6, 1.7}]", None, "0.56", None), + ("CrossProduct[{1,2,3}, {4,5,6}]", None, "{-3, 6, -3}", None), + ( + "CrossProduct[{-1.4, 0.6, 0.2}, {0.1, 0.6, 1.7}]", + None, + "{0.9, 2.4, -0.9}", + None, + ), + ("ScalarTripleProduct[{-2,3,1},{0,4,0},{-1,3,3}]", None, "-20", None), + ( + "ScalarTripleProduct[{-1.4,0.6,0.2}, {0.1,0.6,1.7}, {0.7,-1.5,-0.2}]", + None, + "-2.79", + None, + ), + ( + "last=CoordinatesToCartesian[{2, Pi, 3}, Spherical]", + None, + "{0, 0, -2}", + None, + ), + ("CoordinatesFromCartesian[last, Spherical]", None, "{2, Pi, 0}", None), + ( + "last=CoordinatesToCartesian[{2, Pi, 3}, Cylindrical]", + None, + "{-2, 0, 3}", + None, + ), + ("CoordinatesFromCartesian[last, Cylindrical]", None, "{2, Pi, 3}", None), + ## Needs Sin/Cos exact value (PR #100) for these tests to pass + # ('last=CoordinatesToCartesian[{2, Pi / 4, Pi / 3}, Spherical]', None, + # '{Sqrt[2] / 2, Sqrt[2] Sqrt[3] / 2, Sqrt[2]}', None), + # ('CoordinatesFromCartesian[last, Spherical]', None, + # '{2, Pi / 4, Pi / 3}', None,), + # ('last=CoordinatesToCartesian[{2, Pi / 4, -1}, Cylindrical]', None, + # '{Sqrt[2], Sqrt[2], -1}', None), + # ('last=CoordinatesFromCartesian[last, Cylindrical]', None, + # '{2, Pi / 4, -1}', None), + ## Continue... + ( + "CoordinatesToCartesian[{0.27, 0.51, 0.92}, Cylindrical]", + None, + "{0.235641, 0.131808, 0.92}", + None, + ), + ( + "CoordinatesToCartesian[{0.27, 0.51, 0.92}, Spherical]", + None, + "{0.0798519, 0.104867, 0.235641}", + None, + ), + ("Coordinates[]", None, "{Xx, Yy, Zz}", None), + ("Coordinates[Spherical]", None, "{Rr, Ttheta, Pphi}", None), + ("SetCoordinates[Cylindrical]", None, "Cylindrical[Rr, Ttheta, Zz]", None), + ("Coordinates[]", None, "{Rr, Ttheta, Zz}", None), + ("CoordinateSystem", None, "Cylindrical", None), + ("Parameters[]", None, "{}", None), + ( + "CoordinateRanges[]", + None, + ## And[a Date: Sat, 19 Aug 2023 07:37:58 -0300 Subject: [PATCH 04/51] revert #> Close -> >> Close when is not instructive --- mathics/builtin/files_io/files.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 29c5d21e3..200d53406 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -204,7 +204,7 @@ class Close(Builtin): Closing a file doesn't delete it from the filesystem >> DeleteFile[file]; - >> Clear[file] + #> Clear[file] """ summary_text = "close a stream" @@ -502,6 +502,8 @@ class OpenRead(_OpenAction): >> OpenRead["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"] = InputStream[...] + + The stream must be closed after using it to release the resource: >> Close[%]; S> Close[OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"]]; @@ -783,7 +785,7 @@ class Read(Builtin): = abc123 >> Read[stream, String] = EndOfFile - >> Close[stream]; + #> Close[stream]; ## Reading Words >> stream = StringToStream["abc 123"]; @@ -793,7 +795,7 @@ class Read(Builtin): = 123 >> Read[stream, Word] = EndOfFile - >> Close[stream]; + #> Close[stream]; ## Number >> stream = StringToStream["123, 4"]; >> Read[stream, Number] @@ -802,7 +804,7 @@ class Read(Builtin): = 4 >> Read[stream, Number] = EndOfFile - >> Close[stream]; + #> Close[stream]; ## HoldExpression: @@ -815,7 +817,7 @@ class Read(Builtin): >> Read[stream, Expression] = 5 - >> Close[stream]; + #> Close[stream]; Reading a comment however will return the empty list: >> stream = StringToStream["(* ::Package:: *)"]; @@ -823,7 +825,7 @@ class Read(Builtin): >> Read[stream, Hold[Expression]] = {} - >> Close[stream]; + #> Close[stream]; ## Multiple types >> stream = StringToStream["123 abc"]; @@ -831,7 +833,7 @@ class Read(Builtin): = {123, abc} >> Read[stream, {Number, Word}] = EndOfFile - >> Close[stream]; + #> Close[stream]; Multiple lines: >> stream = StringToStream["\\"Tengo una\\nvaca lechera.\\""]; Read[stream] @@ -1105,7 +1107,7 @@ class ReadList(Read): = {abc123} >> InputForm[%] = {"abc123"} - >> Close[stream]; + #> Close[stream]; """ # TODO @@ -1343,7 +1345,7 @@ class Skip(Read): >> Skip[stream, Word] >> Read[stream, Word] = c - >> Close[stream]; + #> Close[stream]; >> stream = StringToStream["a b c d"]; >> Read[stream, Word] @@ -1353,7 +1355,7 @@ class Skip(Read): = d >> Skip[stream, Word] = EndOfFile - >> Close[stream]; + #> Close[stream]; """ messages = { @@ -1416,7 +1418,7 @@ class Find(Read): = in manuscript, leads me to expect that the element uranium may be turned into >> Find[stream, "uranium"] = become possible to set up a nuclear chain reaction in a large mass of uranium, - >> Close[stream] + #> Close[stream] = ... >> stream = OpenRead["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"]; @@ -1424,7 +1426,7 @@ class Find(Read): = a new and important source of energy in the immediate future. Certain aspects >> Find[stream, {"energy", "power"} ] = by which vast amounts of power and large quantities of new radium-like - >> Close[stream] + #> Close[stream] = ... """ @@ -1510,6 +1512,7 @@ class StringToStream(Builtin): >> strm = StringToStream["abc 123"] = InputStream[String, ...] + The stream must be closed after using it, to release the resource: >> Close[strm]; """ @@ -1610,6 +1613,7 @@ class Write(Builtin): = ... >> Write[stream, 10 x + 15 y ^ 2] >> Write[stream, 3 Sin[z]] + The stream must be closed in order to use the file again: >> Close[stream]; >> stream = OpenRead[%]; >> ReadList[stream] From 3229c8e4c3132b5374d47022221ae737ff223db4 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 9 Sep 2023 17:39:23 -0300 Subject: [PATCH 05/51] moving private doctests to pytest in mathics.builtin.specialfns, mathics.builtin.numbers.linalg and mathics.builtin.numbers.numbertheory --- mathics/builtin/numbers/linalg.py | 69 ----------- mathics/builtin/numbers/numbertheory.py | 79 +------------ mathics/builtin/quantities.py | 1 - mathics/builtin/specialfns/bessel.py | 28 ----- mathics/builtin/specialfns/gamma.py | 18 --- mathics/builtin/specialfns/orthogonal.py | 3 - test/builtin/numbers/test_linalg.py | 136 ++++++++++++++++++++++ test/builtin/numbers/test_numbertheory.py | 70 +++++++++++ test/builtin/specialfns/test_bessel.py | 64 +++++++++- test/builtin/specialfns/test_gamma.py | 46 ++++++++ 10 files changed, 318 insertions(+), 196 deletions(-) create mode 100644 test/builtin/numbers/test_numbertheory.py create mode 100644 test/builtin/specialfns/test_gamma.py diff --git a/mathics/builtin/numbers/linalg.py b/mathics/builtin/numbers/linalg.py index 1a9da9992..d670c4727 100644 --- a/mathics/builtin/numbers/linalg.py +++ b/mathics/builtin/numbers/linalg.py @@ -124,10 +124,6 @@ class Eigenvalues(Builtin): >> Eigenvalues[{{7, 1}, {-4, 3}}] = {5, 5} - - #> Eigenvalues[{{1, 0}, {0}}] - : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix. - = Eigenvalues[{{1, 0}, {0}}] """ messages = { @@ -221,9 +217,6 @@ class Eigenvectors(Builtin): >> Eigenvectors[{{0.1, 0.2}, {0.8, 0.5}}] = ... ### = {{-0.355518, -1.15048}, {-0.62896, 0.777438}} - - #> Eigenvectors[{{-2, 1, -1}, {-3, 2, 1}, {-1, 1, 0}}] - = {{1, 7, 3}, {1, 1, 0}, {0, 0, 0}} """ messages = { @@ -365,18 +358,6 @@ class LeastSquares(Builtin): >> LeastSquares[{{1, 1, 1}, {1, 1, 2}}, {1, 3}] : Solving for underdetermined system not implemented. = LeastSquares[{{1, 1, 1}, {1, 1, 2}}, {1, 3}] - - ## Inconsistent system - ideally we'd print a different message - #> LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}] - : Solving for underdetermined system not implemented. - = LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}] - - #> LeastSquares[{1, {2}}, {1, 2}] - : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix. - = LeastSquares[{1, {2}}, {1, 2}] - #> LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}] - : Argument {1, {2}} at position 2 is not a non-empty rectangular matrix. - = LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}] """ messages = { @@ -510,13 +491,6 @@ class LinearSolve(Builtin): >> LinearSolve[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {1, -2, 3}] : Linear equation encountered that has no solution. = LinearSolve[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {1, -2, 3}] - - #> LinearSolve[{1, {2}}, {1, 2}] - : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix. - = LinearSolve[{1, {2}}, {1, 2}] - #> LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}] - : Argument {1, {2}} at position 2 is not a non-empty rectangular matrix. - = LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}] """ messages = { @@ -582,13 +556,6 @@ class MatrixExp(Builtin): >> MatrixExp[{{1.5, 0.5}, {0.5, 2.0}}] = {{5.16266, 3.02952}, {3.02952, 8.19218}} - - #> MatrixExp[{{a, 0}, {0, b}}] - = {{E ^ a, 0}, {0, E ^ b}} - - #> MatrixExp[{{1, 0}, {0}}] - : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix. - = MatrixExp[{{1, 0}, {0}}] """ messages = { @@ -628,13 +595,6 @@ class MatrixPower(Builtin): >> MatrixPower[{{1, 2}, {2, 5}}, -3] = {{169, -70}, {-70, 29}} - - #> MatrixPower[{{0, x}, {0, 0}}, n] - = MatrixPower[{{0, x}, {0, 0}}, n] - - #> MatrixPower[{{1, 0}, {0}}, 2] - : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix. - = MatrixPower[{{1, 0}, {0}}, 2] """ messages = { @@ -681,10 +641,6 @@ class MatrixRank(Builtin): = 3 >> MatrixRank[{{a, b}, {3 a, 3 b}}] = 1 - - #> MatrixRank[{{1, 0}, {0}}] - : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix. - = MatrixRank[{{1, 0}, {0}}] """ messages = { @@ -721,10 +677,6 @@ class NullSpace(Builtin): = {} >> MatrixRank[A] = 3 - - #> NullSpace[{1, {2}}] - : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix. - = NullSpace[{1, {2}}] """ messages = { @@ -764,10 +716,6 @@ class PseudoInverse(Builtin): >> PseudoInverse[{{1.0, 2.5}, {2.5, 1.0}}] = {{-0.190476, 0.47619}, {0.47619, -0.190476}} - - #> PseudoInverse[{1, {2}}] - : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix. - = PseudoInverse[{1, {2}}] """ messages = { @@ -798,10 +746,6 @@ class QRDecomposition(Builtin): >> QRDecomposition[{{1, 2}, {3, 4}, {5, 6}}] = {{{Sqrt[35] / 35, 3 Sqrt[35] / 35, Sqrt[35] / 7}, {13 Sqrt[210] / 210, 2 Sqrt[210] / 105, -Sqrt[210] / 42}}, {{Sqrt[35], 44 Sqrt[35] / 35}, {0, 2 Sqrt[210] / 35}}} - - #> QRDecomposition[{1, {2}}] - : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix. - = QRDecomposition[{1, {2}}] """ messages = { @@ -844,10 +788,6 @@ class RowReduce(Builtin): . 0 1 2 . . 0 0 0 - - #> RowReduce[{{1, 0}, {0}}] - : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix. - = RowReduce[{{1, 0}, {0}}] """ messages = { @@ -881,15 +821,6 @@ class SingularValueDecomposition(Builtin): >> SingularValueDecomposition[{{1.5, 2.0}, {2.5, 3.0}}] = {{{0.538954, 0.842335}, {0.842335, -0.538954}}, {{4.63555, 0.}, {0., 0.107862}}, {{0.628678, 0.777666}, {-0.777666, 0.628678}}} - - - #> SingularValueDecomposition[{{3/2, 2}, {5/2, 3}}] - : Symbolic SVD is not implemented, performing numerically. - = {{{0.538954, 0.842335}, {0.842335, -0.538954}}, {{4.63555, 0.}, {0., 0.107862}}, {{0.628678, 0.777666}, {-0.777666, 0.628678}}} - - #> SingularValueDecomposition[{1, {2}}] - : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix. - = SingularValueDecomposition[{1, {2}}] """ # Sympy lacks symbolic SVD diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 85687c3db..9e9972238 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -91,14 +91,6 @@ class Divisors(Builtin): = {1, 2, 4, 8, 11, 16, 22, 32, 44, 64, 88, 176, 352, 704} >> Divisors[{87, 106, 202, 305}] = {{1, 3, 29, 87}, {1, 2, 53, 106}, {1, 2, 101, 202}, {1, 5, 61, 305}} - #> Divisors[0] - = Divisors[0] - #> Divisors[{-206, -502, -1702, 9}] - = {{1, 2, 103, 206}, {1, 2, 251, 502}, {1, 2, 23, 37, 46, 74, 851, 1702}, {1, 3, 9}} - #> Length[Divisors[1000*369]] - = 96 - #> Length[Divisors[305*176*369*100]] - = 672 """ # TODO: support GaussianIntegers @@ -275,21 +267,6 @@ class FractionalPart(Builtin): >> FractionalPart[-5.25] = -0.25 - - #> FractionalPart[b] - = FractionalPart[b] - - #> FractionalPart[{-2.4, -2.5, -3.0}] - = {-0.4, -0.5, 0.} - - #> FractionalPart[14/32] - = 7 / 16 - - #> FractionalPart[4/(1 + 3 I)] - = 2 / 5 - I / 5 - - #> FractionalPart[Pi^20] - = -8769956796 + Pi ^ 20 """ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_READ_PROTECTED | A_PROTECTED @@ -370,47 +347,6 @@ class MantissaExponent(Builtin): >> MantissaExponent[10, b] = MantissaExponent[10, b] - - #> MantissaExponent[E, Pi] - = {E / Pi, 1} - - #> MantissaExponent[Pi, Pi] - = {1 / Pi, 2} - - #> MantissaExponent[5/2 + 3, Pi] - = {11 / (2 Pi ^ 2), 2} - - #> MantissaExponent[b] - = MantissaExponent[b] - - #> MantissaExponent[17, E] - = {17 / E ^ 3, 3} - - #> MantissaExponent[17., E] - = {0.84638, 3} - - #> MantissaExponent[Exp[Pi], 2] - = {E ^ Pi / 32, 5} - - #> MantissaExponent[3 + 2 I, 2] - : The value 3 + 2 I is not a real number - = MantissaExponent[3 + 2 I, 2] - - #> MantissaExponent[25, 0.4] - : Base 0.4 is not a real number greater than 1. - = MantissaExponent[25, 0.4] - - #> MantissaExponent[0.0000124] - = {0.124, -4} - - #> MantissaExponent[0.0000124, 2] - = {0.812646, -16} - - #> MantissaExponent[0] - = {0, 0} - - #> MantissaExponent[0, 2] - = {0, 0} """ attributes = A_LISTABLE | A_PROTECTED @@ -674,9 +610,6 @@ class PrimePowerQ(Builtin): >> PrimePowerQ[371293] = True - - #> PrimePowerQ[1] - = False """ attributes = A_LISTABLE | A_PROTECTED | A_READ_PROTECTED @@ -687,19 +620,19 @@ class PrimePowerQ(Builtin): # TODO: GaussianIntegers option """ - #> PrimePowerQ[5, GaussianIntegers -> True] + ##> PrimePowerQ[5, GaussianIntegers -> True] = False """ # TODO: Complex args """ - #> PrimePowerQ[{3 + I, 3 - 2 I, 3 + 4 I, 9 + 7 I}] + ##> PrimePowerQ[{3 + I, 3 - 2 I, 3 + 4 I, 9 + 7 I}] = {False, True, True, False} """ # TODO: Gaussian rationals """ - #> PrimePowerQ[2/125 - 11 I/125] + ##> PrimePowerQ[2/125 - 11 I/125] = True """ @@ -740,12 +673,6 @@ class RandomPrime(Builtin): >> RandomPrime[{10,30}, {2,5}] = ... - - #> RandomPrime[{10,12}, {2,2}] - = {{11, 11}, {11, 11}} - - #> RandomPrime[2, {3,2}] - = {{2, 2}, {2, 2}, {2, 2}} """ messages = { diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py index e4de27392..be77227ef 100644 --- a/mathics/builtin/quantities.py +++ b/mathics/builtin/quantities.py @@ -447,7 +447,6 @@ def eval_list_to_base_unit(self, expr, evaluation: Evaluation): def eval_quantity_to_base_unit(self, mag, unit, evaluation: Evaluation): "UnitConvert[Quantity[mag_, unit_]]" - print("convert", mag, unit, "to basic units") try: return convert_units(mag, unit, evaluation=evaluation) except ValueError: diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index e2e2455da..ebe20fea2 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -24,7 +24,6 @@ class _Bessel(MPMathFunction): - attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED nargs = {2} @@ -109,18 +108,6 @@ class AiryAiZero(Builtin): >> N[AiryAiZero[1]] = -2.33811 - - #> AiryAiZero[1] - = AiryAiZero[1] - - #> AiryAiZero[1.] - = AiryAiZero[1.] - - #> AiryAi[AiryAiZero[1]] - = 0 - - #> N[AiryAiZero[2], 100] - = -4.087949444130970616636988701457391060224764699108529754984160876025121946836047394331169160758270562 """ # TODO: 'AiryAiZero[$k$, $x0$]' - $k$th zero less than x0 @@ -235,18 +222,6 @@ class AiryBiZero(Builtin): >> N[AiryBiZero[1]] = -1.17371 - - #> AiryBiZero[1] - = AiryBiZero[1] - - #> AiryBiZero[1.] - = AiryBiZero[1.] - - #> AiryBi[AiryBiZero[1]] - = 0 - - #> N[AiryBiZero[2], 100] - = -3.271093302836352715680228240166413806300935969100284801485032396261130864238742879252000673830055014 """ # TODO: 'AiryBiZero[$k$, $x0$]' - $k$th zero less than x0 @@ -380,9 +355,6 @@ class BesselJ(_Bessel): >> BesselJ[0, 5.2] = -0.11029 - #> BesselJ[2.5, 1] - = 0.0494968 - >> D[BesselJ[n, z], z] = -BesselJ[1 + n, z] / 2 + BesselJ[-1 + n, z] / 2 diff --git a/mathics/builtin/specialfns/gamma.py b/mathics/builtin/specialfns/gamma.py index 33746fd11..7369e0ba6 100644 --- a/mathics/builtin/specialfns/gamma.py +++ b/mathics/builtin/specialfns/gamma.py @@ -155,8 +155,6 @@ class Factorial(PostfixOperator, MPMathFunction): >> !a! //FullForm = Not[Factorial[a]] - #> 0! - = 1 """ attributes = A_NUMERIC_FUNCTION | A_PROTECTED @@ -301,22 +299,6 @@ class Gamma(MPMathMultiFunction): Both 'Gamma' and 'Factorial' functions are continuous: >> Plot[{Gamma[x], x!}, {x, 0, 4}] = -Graphics- - - ## Issue 203 - #> N[Gamma[24/10], 100] - = 1.242169344504305404913070252268300492431517240992022966055507541481863694148882652446155342679460339 - #> N[N[Gamma[24/10],100]/N[Gamma[14/10],100],100] - = 1.400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - #> % // Precision - = 100. - - #> Gamma[1.*^20] - : Overflow occurred in computation. - = Overflow[] - - ## Needs mpmath support for lowergamma - #> Gamma[1., 2.] - = Gamma[1., 2.] """ mpmath_names = { diff --git a/mathics/builtin/specialfns/orthogonal.py b/mathics/builtin/specialfns/orthogonal.py index 464b148f9..bd2cb002e 100644 --- a/mathics/builtin/specialfns/orthogonal.py +++ b/mathics/builtin/specialfns/orthogonal.py @@ -269,9 +269,6 @@ class SphericalHarmonicY(MPMathFunction): ## Results depend on sympy version >> SphericalHarmonicY[3, 1, theta, phi] = ... - - #> SphericalHarmonicY[1,1,x,y] - = -Sqrt[6] E ^ (I y) Sin[x] / (4 Sqrt[Pi]) """ nargs = {4} diff --git a/test/builtin/numbers/test_linalg.py b/test/builtin/numbers/test_linalg.py index 73a914082..3494e65e2 100644 --- a/test/builtin/numbers/test_linalg.py +++ b/test/builtin/numbers/test_linalg.py @@ -88,3 +88,139 @@ def test_inverse(str_expr, str_expected, fail_msg, warnings): check_evaluation( str_expr, str_expected, failure_message="", expected_messages=warnings ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "Eigenvalues[{{1, 0}, {0}}]", + ( + "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.", + ), + "Eigenvalues[{{1, 0}, {0}}]", + None, + ), + ( + "Eigenvectors[{{-2, 1, -1}, {-3, 2, 1}, {-1, 1, 0}}]", + None, + "{{1, 7, 3}, {1, 1, 0}, {0, 0, 0}}", + None, + ), + ## Inconsistent system - ideally we'd print a different message + ( + "LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}]", + ("Solving for underdetermined system not implemented.",), + "LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}]", + None, + ), + ( + "LeastSquares[{1, {2}}, {1, 2}]", + ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",), + "LeastSquares[{1, {2}}, {1, 2}]", + None, + ), + ( + "LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}]", + ("Argument {1, {2}} at position 2 is not a non-empty rectangular matrix.",), + "LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}]", + None, + ), + ( + "LinearSolve[{1, {2}}, {1, 2}]", + ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",), + "LinearSolve[{1, {2}}, {1, 2}]", + None, + ), + ( + "LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}]", + ("Argument {1, {2}} at position 2 is not a non-empty rectangular matrix.",), + "LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}]", + None, + ), + ("MatrixExp[{{a, 0}, {0, b}}]", None, "{{E ^ a, 0}, {0, E ^ b}}", None), + ( + "MatrixExp[{{1, 0}, {0}}]", + ( + "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.", + ), + "MatrixExp[{{1, 0}, {0}}]", + None, + ), + ( + "MatrixPower[{{0, x}, {0, 0}}, n]", + None, + "MatrixPower[{{0, x}, {0, 0}}, n]", + None, + ), + ( + "MatrixPower[{{1, 0}, {0}}, 2]", + ( + "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.", + ), + "MatrixPower[{{1, 0}, {0}}, 2]", + None, + ), + ( + "MatrixRank[{{1, 0}, {0}}]", + ( + "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.", + ), + "MatrixRank[{{1, 0}, {0}}]", + None, + ), + ( + "NullSpace[{1, {2}}]", + ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",), + "NullSpace[{1, {2}}]", + None, + ), + ( + "PseudoInverse[{1, {2}}]", + ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",), + "PseudoInverse[{1, {2}}]", + None, + ), + ( + "QRDecomposition[{1, {2}}]", + ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",), + "QRDecomposition[{1, {2}}]", + None, + ), + ( + "RowReduce[{{1, 0}, {0}}]", + ( + "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.", + ), + "RowReduce[{{1, 0}, {0}}]", + None, + ), + ( + "SingularValueDecomposition[{{3/2, 2}, {5/2, 3}}]", + ("Symbolic SVD is not implemented, performing numerically.",), + ( + "{{{0.538954, 0.842335}, {0.842335, -0.538954}}, " + "{{4.63555, 0.}, {0., 0.107862}}, " + "{{0.628678, 0.777666}, {-0.777666, 0.628678}}}" + ), + None, + ), + ( + "SingularValueDecomposition[{1, {2}}]", + ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",), + "SingularValueDecomposition[{1, {2}}]", + None, + ), + ], +) +def test_private_doctests_linalg(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/numbers/test_numbertheory.py b/test/builtin/numbers/test_numbertheory.py new file mode 100644 index 000000000..8e5e149b4 --- /dev/null +++ b/test/builtin/numbers/test_numbertheory.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for mathics.builtins.numbers.numbertheory +""" +from test.helper import check_evaluation + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("Divisors[0]", None, "Divisors[0]", None), + ( + "Divisors[{-206, -502, -1702, 9}]", + None, + ( + "{{1, 2, 103, 206}, " + "{1, 2, 251, 502}, " + "{1, 2, 23, 37, 46, 74, 851, 1702}, " + "{1, 3, 9}}" + ), + None, + ), + ("Length[Divisors[1000*369]]", None, "96", None), + ("Length[Divisors[305*176*369*100]]", None, "672", None), + ("FractionalPart[b]", None, "FractionalPart[b]", None), + ("FractionalPart[{-2.4, -2.5, -3.0}]", None, "{-0.4, -0.5, 0.}", None), + ("FractionalPart[14/32]", None, "7 / 16", None), + ("FractionalPart[4/(1 + 3 I)]", None, "2 / 5 - I / 5", None), + ("FractionalPart[Pi^20]", None, "-8769956796 + Pi ^ 20", None), + ("MantissaExponent[E, Pi]", None, "{E / Pi, 1}", None), + ("MantissaExponent[Pi, Pi]", None, "{1 / Pi, 2}", None), + ("MantissaExponent[5/2 + 3, Pi]", None, "{11 / (2 Pi ^ 2), 2}", None), + ("MantissaExponent[b]", None, "MantissaExponent[b]", None), + ("MantissaExponent[17, E]", None, "{17 / E ^ 3, 3}", None), + ("MantissaExponent[17., E]", None, "{0.84638, 3}", None), + ("MantissaExponent[Exp[Pi], 2]", None, "{E ^ Pi / 32, 5}", None), + ( + "MantissaExponent[3 + 2 I, 2]", + ("The value 3 + 2 I is not a real number",), + "MantissaExponent[3 + 2 I, 2]", + None, + ), + ( + "MantissaExponent[25, 0.4]", + ("Base 0.4 is not a real number greater than 1.",), + "MantissaExponent[25, 0.4]", + None, + ), + ("MantissaExponent[0.0000124]", None, "{0.124, -4}", None), + ("MantissaExponent[0.0000124, 2]", None, "{0.812646, -16}", None), + ("MantissaExponent[0]", None, "{0, 0}", None), + ("MantissaExponent[0, 2]", None, "{0, 0}", None), + ("PrimePowerQ[1]", None, "False", None), + ("RandomPrime[{10,12}, {2,2}]", None, "{{11, 11}, {11, 11}}", None), + ("RandomPrime[2, {3,2}]", None, "{{2, 2}, {2, 2}, {2, 2}}", None), + ], +) +def test_private_doctests_numbertheory(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/specialfns/test_bessel.py b/test/builtin/specialfns/test_bessel.py index b6201d76e..6b7296763 100644 --- a/test/builtin/specialfns/test_bessel.py +++ b/test/builtin/specialfns/test_bessel.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ -Unit tests for mathics.builtins.arithmetic.bessel +Unit tests for mathics.builtins.specialfns.bessel and +mathics.builtins.specialfns.orthogonal """ from test.helper import check_evaluation @@ -30,3 +31,64 @@ def test_add(str_expr, str_expected, assert_failure_msg): check_evaluation( str_expr, str_expected, hold_expected=True, failure_message=assert_failure_msg ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("AiryAiZero[1]", None, "AiryAiZero[1]", None), + ("AiryAiZero[1.]", None, "AiryAiZero[1.]", None), + ("AiryAi[AiryAiZero[1]]", None, "0", None), + ( + "N[AiryAiZero[2], 100]", + None, + "-4.087949444130970616636988701457391060224764699108529754984160876025121946836047394331169160758270562", + None, + ), + ("AiryBiZero[1]", None, "AiryBiZero[1]", None), + ("AiryBiZero[1.]", None, "AiryBiZero[1.]", None), + ("AiryBi[AiryBiZero[1]]", None, "0", None), + ( + "N[AiryBiZero[2], 100]", + None, + "-3.271093302836352715680228240166413806300935969100284801485032396261130864238742879252000673830055014", + None, + ), + ("BesselJ[2.5, 1]", None, "0.0494968", None), + ], +) +def test_private_doctests_bessel(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "SphericalHarmonicY[1,1,x,y]", + None, + "-Sqrt[6] E ^ (I y) Sin[x] / (4 Sqrt[Pi])", + None, + ), + ], +) +def test_private_doctests_orthogonal(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/specialfns/test_gamma.py b/test/builtin/specialfns/test_gamma.py new file mode 100644 index 000000000..b5d1d4148 --- /dev/null +++ b/test/builtin/specialfns/test_gamma.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for mathics.builtins.specialfns.gamma +""" +from test.helper import check_evaluation + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("0!", None, "1", None), + ( + "N[Gamma[24/10], 100]", + None, + "1.242169344504305404913070252268300492431517240992022966055507541481863694148882652446155342679460339", + "Issue 203", + ), + ( + "res=N[N[Gamma[24/10],100]/N[Gamma[14/10],100],100]", + None, + "1.400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Issue 203", + ), + ("res // Precision", None, "100.", None), + ( + "Gamma[1.*^20]", + ("Overflow occurred in computation.",), + "Overflow[]", + "Overflow", + ), + ("Gamma[1., 2.]", None, "Gamma[1., 2.]", "needs mpmath for lowergamma"), + ], +) +def test_private_doctests_gamma(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From 8265c30a47a3e3416758bca18b4d5f15a578e522 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 9 Sep 2023 23:18:38 -0300 Subject: [PATCH 06/51] move private doctests to pytests in mathics.builtin.numbers --- mathics/builtin/numbers/algebra.py | 117 ------------- mathics/builtin/numbers/calculus.py | 60 ------- mathics/builtin/numbers/diffeqns.py | 42 ----- mathics/builtin/numbers/exp.py | 21 --- mathics/builtin/numbers/hyperbolic.py | 5 - mathics/builtin/numbers/integer.py | 4 - mathics/builtin/numbers/randomnumbers.py | 29 ---- mathics/builtin/numbers/trig.py | 24 --- test/builtin/numbers/test_algebra.py | 183 ++++++++++++++++++++- test/builtin/numbers/test_calculus.py | 67 +++++++- test/builtin/numbers/test_hyperbolic.py | 57 ++++++- test/builtin/numbers/test_randomnumbers.py | 67 ++++++++ test/builtin/numbers/test_trig.py | 30 ++++ test/builtin/specialfns/test_bessel.py | 2 +- test/core/parser/test_parser.py | 2 + 15 files changed, 403 insertions(+), 307 deletions(-) diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 3ea0052d9..9af31c295 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -373,12 +373,6 @@ class Apart(Builtin): But it does not touch other expressions: >> Sin[1 / (x ^ 2 - y ^ 2)] // Apart = Sin[1 / (x ^ 2 - y ^ 2)] - - #> Attributes[f] = {HoldAll}; Apart[f[x + x]] - = f[x + x] - - #> Attributes[f] = {}; Apart[f[x + x]] - = f[2 x] """ attributes = A_LISTABLE | A_PROTECTED @@ -504,25 +498,9 @@ class Coefficient(Builtin): >> Coefficient[a x^2 + b y^3 + c x + d y + 5, x, 0] = 5 + b y ^ 3 + d y - ## Errors: - #> Coefficient[x + y + 3] - : Coefficient called with 1 argument; 2 or 3 arguments are expected. - = Coefficient[3 + x + y] - #> Coefficient[x + y + 3, 5] - : 5 is not a valid variable. - = Coefficient[3 + x + y, 5] - - ## This is known bug of Sympy 1.0, next Sympy version will fix it by this commit - ## https://github.com/sympy/sympy/commit/25bf64b64d4d9a2dc563022818d29d06bc740d47 - ## #> Coefficient[x * y, z, 0] - ## = x y - ## ## Sympy 1.0 retuns 0 - ## ## TODO: Support Modulus ## >> Coefficient[(x + 2)^3 + (x + 3)^2, x, 0, Modulus -> 3] ## = 2 - ## #> Coefficient[(x + 2)^3 + (x + 3)^2, x, 0, {Modulus -> 3, Modulus -> 2, Modulus -> 10}] - ## = {2, 1, 7} """ attributes = A_LISTABLE | A_PROTECTED @@ -910,21 +888,11 @@ class CoefficientList(Builtin): = {2 / (-3 + y), 1 / (-3 + y) + 1 / (-2 + y)} >> CoefficientList[(x + y)^3, z] = {(x + y) ^ 3} - #> CoefficientList[x + y, 5] - : 5 is not a valid variable. - = CoefficientList[x + y, 5] - ## Form 2 CoefficientList[poly, {var1, var2, ...}] >> CoefficientList[a x^2 + b y^3 + c x + d y + 5, {x, y}] = {{5, d, 0, b}, {c, 0, 0, 0}, {a, 0, 0, 0}} >> CoefficientList[(x - 2 y + 3 z)^3, {x, y, z}] = {{{0, 0, 0, 27}, {0, 0, -54, 0}, {0, 36, 0, 0}, {-8, 0, 0, 0}}, {{0, 0, 27, 0}, {0, -36, 0, 0}, {12, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 9, 0, 0}, {-6, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{1, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}} - #> CoefficientList[(x - 2 y)^4, {x, 2}] - : 2 is not a valid variable. - = CoefficientList[(x - 2 y) ^ 4, {x, 2}] - #> CoefficientList[x / y, {x, y}] - : x / y is not a polynomial. - = CoefficientList[x / y, {x, y}] """ messages = { @@ -1182,22 +1150,6 @@ class Expand(_Expand): >> Expand[(1 + a)^12, Modulus -> 4] = 1 + 2 a ^ 2 + 3 a ^ 4 + 3 a ^ 8 + 2 a ^ 10 + a ^ 12 - - #> Expand[x, Modulus -> -1] (* copy odd MMA behaviour *) - = 0 - #> Expand[x, Modulus -> x] - : Value of option Modulus -> x should be an integer. - = Expand[x, Modulus -> x] - - #> a(b(c+d)+e) // Expand - = a b c + a b d + a e - - #> (y^2)^(1/2)/(2x+2y)//Expand - = Sqrt[y ^ 2] / (2 x + 2 y) - - - #> 2(3+2x)^2/(5+x^2+3x)^3 // Expand - = 24 x / (5 + 3 x + x ^ 2) ^ 3 + 8 x ^ 2 / (5 + 3 x + x ^ 2) ^ 3 + 18 / (5 + 3 x + x ^ 2) ^ 3 """ summary_text = "expand out products and powers" @@ -1303,15 +1255,6 @@ class ExpandDenominator(_Expand): >> ExpandDenominator[(a + b) ^ 2 / ((c + d)^2 (e + f))] = (a + b) ^ 2 / (c ^ 2 e + c ^ 2 f + 2 c d e + 2 c d f + d ^ 2 e + d ^ 2 f) - - ## Modulus option - #> ExpandDenominator[1 / (x + y)^3, Modulus -> 3] - = 1 / (x ^ 3 + y ^ 3) - #> ExpandDenominator[1 / (x + y)^6, Modulus -> 4] - = 1 / (x ^ 6 + 2 x ^ 5 y + 3 x ^ 4 y ^ 2 + 3 x ^ 2 y ^ 4 + 2 x y ^ 5 + y ^ 6) - - #> ExpandDenominator[2(3+2x)^2/(5+x^2+3x)^3] - = 2 (3 + 2 x) ^ 2 / (125 + 225 x + 210 x ^ 2 + 117 x ^ 3 + 42 x ^ 4 + 9 x ^ 5 + x ^ 6) """ summary_text = "expand just the denominator of a rational expression" @@ -1354,11 +1297,6 @@ class Exponent(Builtin): = -Infinity >> Exponent[1, x] = 0 - - ## errors: - #> Exponent[x^2] - : Exponent called with 1 argument; 2 or 3 arguments are expected. - = Exponent[x ^ 2] """ attributes = A_LISTABLE | A_PROTECTED @@ -1422,10 +1360,6 @@ class Factor(Builtin): You can use Factor to find when a polynomial is zero: >> x^2 - x == 0 // Factor = x (-1 + x) == 0 - - ## Issue659 - #> Factor[{x+x^2}] - = {x (1 + x)} """ attributes = A_LISTABLE | A_PROTECTED @@ -1467,9 +1401,6 @@ class FactorTermsList(Builtin): = {2, -1 + x ^ 2} >> FactorTermsList[x^2 - 2 x + 1] = {1, 1 - 2 x + x ^ 2} - #> FactorTermsList[2 x^2 - 2, x] - = {2, 1, -1 + x ^ 2} - >> f = 3 (-1 + 2 x) (-1 + y) (1 - a) = 3 (-1 + 2 x) (-1 + y) (1 - a) >> FactorTermsList[f] @@ -1775,17 +1706,6 @@ class MinimalPolynomial(Builtin): = -2 - 2 x ^ 2 + x ^ 4 >> MinimalPolynomial[Sqrt[I + Sqrt[6]], x] = 49 - 10 x ^ 4 + x ^ 8 - - #> MinimalPolynomial[7a, x] - : 7 a is not an explicit algebraic number. - = MinimalPolynomial[7 a, x] - #> MinimalPolynomial[3x^3 + 2x^2 + y^2 + ab, x] - : ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2 is not an explicit algebraic number. - = MinimalPolynomial[ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2, x] - - ## PurePoly - #> MinimalPolynomial[Sqrt[2 + Sqrt[3]]] - = 1 - 4 #1 ^ 2 + #1 ^ 4 """ attributes = A_LISTABLE | A_PROTECTED @@ -1874,37 +1794,6 @@ class PolynomialQ(Builtin): = True >> PolynomialQ[x^2 + axy^2 - bSin[c], {a, b, c}] = False - - #> PolynomialQ[x, x, y] - : PolynomialQ called with 3 arguments; 1 or 2 arguments are expected. - = PolynomialQ[x, x, y] - - ## Always return True if argument is Null - #> PolynomialQ[x^3 - 2 x/y + 3xz,] - : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of ""). - = True - #> PolynomialQ[, {x, y, z}] - : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of ""). - = True - #> PolynomialQ[, ] - : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of ""). - : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of ""). - = True - - ## TODO: MMA and Sympy handle these cases differently - ## #> PolynomialQ[x^(1/2) + 6xyz] - ## : No variable is not supported in PolynomialQ. - ## = True - ## #> PolynomialQ[x^(1/2) + 6xyz, {}] - ## : No variable is not supported in PolynomialQ. - ## = True - - ## #> PolynomialQ[x^3 - 2 x/y + 3xz] - ## : No variable is not supported in PolynomialQ. - ## = False - ## #> PolynomialQ[x^3 - 2 x/y + 3xz, {}] - ## : No variable is not supported in PolynomialQ. - ## = False """ messages = { @@ -1994,9 +1883,6 @@ class Together(Builtin): But it does not touch other functions: >> Together[f[a / c + b / c]] = f[a / c + b / c] - - #> f[x]/x+f[x]/x^2//Together - = f[x] (1 + x) / x ^ 2 """ attributes = A_LISTABLE | A_PROTECTED @@ -2030,9 +1916,6 @@ class Variables(Builtin): = {a, b, c, x, y} >> Variables[x + Sin[y]] = {x, Sin[y]} - ## failing test case from MMA docs - #> Variables[E^x] - = {} """ summary_text = "list of variables in a polynomial" diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 987d201a0..5bc6653f5 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -172,24 +172,6 @@ class D(SympyFunction): Hesse matrix: >> D[Sin[x] * Cos[y], {{x,y}, 2}] = {{-Cos[y] Sin[x], -Cos[x] Sin[y]}, {-Cos[x] Sin[y], -Cos[y] Sin[x]}} - - #> D[2/3 Cos[x] - 1/3 x Cos[x] Sin[x] ^ 2,x]//Expand - = -2 x Cos[x] ^ 2 Sin[x] / 3 + x Sin[x] ^ 3 / 3 - 2 Sin[x] / 3 - Cos[x] Sin[x] ^ 2 / 3 - - #> D[f[#1], {#1,2}] - = f''[#1] - #> D[(#1&)[t],{t,4}] - = 0 - - #> Attributes[f] ={HoldAll}; Apart[f''[x + x]] - = f''[2 x] - - #> Attributes[f] = {}; Apart[f''[x + x]] - = f''[2 x] - - ## Issue #375 - #> D[{#^2}, #] - = {2 #1} """ # TODO @@ -416,16 +398,6 @@ class Derivative(PostfixOperator, SympyFunction): = Derivative[2, 1][h] >> Derivative[2, 0, 1, 0][h[g]] = Derivative[2, 0, 1, 0][h[g]] - - ## Parser Tests - #> Hold[f''] // FullForm - = Hold[Derivative[2][f]] - #> Hold[f ' '] // FullForm - = Hold[Derivative[2][f]] - #> Hold[f '' ''] // FullForm - = Hold[Derivative[4][f]] - #> Hold[Derivative[x][4] '] // FullForm - = Hold[Derivative[1][Derivative[x][4]]] """ attributes = A_N_HOLD_ALL @@ -864,12 +836,8 @@ class FindRoot(_BaseFinder): = FindRoot[Sin[x] - x, {x, 0}] - #> FindRoot[2.5==x,{x,0}] - = {x -> 2.5} - >> FindRoot[x^2 - 2, {x, 1,3}, Method->"Secant"] = {x -> 1.41421} - """ rules = { @@ -970,20 +938,6 @@ class Integrate(SympyFunction): >> Integrate[f[x], {x, a, b}] // TeXForm = \int_a^b f\left[x\right] \, dx - #> DownValues[Integrate] - = {} - #> Definition[Integrate] - = Attributes[Integrate] = {Protected, ReadProtected} - . - . Options[Integrate] = {Assumptions -> $Assumptions, GenerateConditions -> Automatic, PrincipalValue -> False} - #> Integrate[Hold[x + x], {x, a, b}] - = Integrate[Hold[x + x], {x, a, b}] - #> Integrate[sin[x], x] - = Integrate[sin[x], x] - - #> Integrate[x ^ 3.5 + x, x] - = x ^ 2 / 2 + 0.222222 x ^ 4.5 - Sometimes there is a loss of precision during integration. You can check the precision of your result with the following sequence of commands. @@ -992,20 +946,6 @@ class Integrate(SympyFunction): >> % // Precision = MachinePrecision - #> Integrate[1/(x^5+1), x] - = RootSum[1 + 5 #1 + 25 #1 ^ 2 + 125 #1 ^ 3 + 625 #1 ^ 4&, Log[x + 5 #1] #1&] + Log[1 + x] / 5 - - #> Integrate[ArcTan(x), x] - = x ^ 2 ArcTan / 2 - #> Integrate[E[x], x] - = Integrate[E[x], x] - - #> Integrate[Exp[-(x/2)^2],{x,-Infinity,+Infinity}] - = 2 Sqrt[Pi] - - #> Integrate[Exp[-1/(x^2)], x] - = x E ^ (-1 / x ^ 2) + Sqrt[Pi] Erf[1 / x] - >> Integrate[ArcSin[x / 3], x] = x ArcSin[x / 3] + Sqrt[9 - x ^ 2] diff --git a/mathics/builtin/numbers/diffeqns.py b/mathics/builtin/numbers/diffeqns.py index 383e5322a..9224d81d7 100644 --- a/mathics/builtin/numbers/diffeqns.py +++ b/mathics/builtin/numbers/diffeqns.py @@ -42,48 +42,6 @@ class DSolve(Builtin): >> DSolve[D[y[x, t], t] + 2 D[y[x, t], x] == 0, y[x, t], {x, t}] = {{y[x, t] -> C[1][x - 2 t]}} - - ## FIXME: sympy solves this as `Function[{x}, C[1] + Integrate[ArcSin[f[2 x]], x]]` - ## #> Attributes[f] = {HoldAll}; - ## #> DSolve[f[x + x] == Sin[f'[x]], f, x] - ## : To avoid possible ambiguity, the arguments of the dependent variable in f[x + x] == Sin[f'[x]] should literally match the independent variables. - ## = DSolve[f[x + x] == Sin[f'[x]], f, x] - - ## #> Attributes[f] = {}; - ## #> DSolve[f[x + x] == Sin[f'[x]], f, x] - ## : To avoid possible ambiguity, the arguments of the dependent variable in f[2 x] == Sin[f'[x]] should literally match the independent variables. - ## = DSolve[f[2 x] == Sin[f'[x]], f, x] - - #> DSolve[f'[x] == f[x], f, x] // FullForm - = {{Rule[f, Function[{x}, Times[C[1], Power[E, x]]]]}} - - #> DSolve[f'[x] == f[x], f, x] /. {C[1] -> 1} - = {{f -> (Function[{x}, 1 E ^ x])}} - - #> DSolve[f'[x] == f[x], f, x] /. {C -> D} - = {{f -> (Function[{x}, D[1] E ^ x])}} - - #> DSolve[f'[x] == f[x], f, x] /. {C[1] -> C[0]} - = {{f -> (Function[{x}, C[0] E ^ x])}} - - #> DSolve[f[x] == 0, f, {}] - : {} cannot be used as a variable. - = DSolve[f[x] == 0, f, {}] - - ## Order of arguments shoudn't matter - #> DSolve[D[f[x, y], x] == D[f[x, y], y], f, {x, y}] - = {{f -> (Function[{x, y}, C[1][-x - y]])}} - #> DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {x, y}] - = {{f[x, y] -> C[1][-x - y]}} - #> DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {y, x}] - = {{f[x, y] -> C[1][-x - y]}} - """ - - # XXX sympy #11669 test - """ - #> DSolve[\\[Gamma]'[x] == 0, \\[Gamma], x] - : Hit sympy bug #11669. - = ... """ # TODO: GeneratedParameters option diff --git a/mathics/builtin/numbers/exp.py b/mathics/builtin/numbers/exp.py index e4c626e65..7156ef212 100644 --- a/mathics/builtin/numbers/exp.py +++ b/mathics/builtin/numbers/exp.py @@ -176,9 +176,6 @@ class Exp(MPMathFunction): >> Plot[Exp[x], {x, 0, 3}] = -Graphics- - #> Exp[1.*^20] - : Overflow occurred in computation. - = Overflow[] """ rules = { @@ -206,21 +203,6 @@ class Log(MPMathFunction): = Indeterminate >> Plot[Log[x], {x, 0, 5}] = -Graphics- - - #> Log[1000] / Log[10] // Simplify - = 3 - - #> Log[1.4] - = 0.336472 - - #> Log[Exp[1.4]] - = 1.4 - - #> Log[-1.4] - = 0.336472 + 3.14159 I - - #> N[Log[10], 30] - = 2.30258509299404568401799145468 """ summary_text = "logarithm function" @@ -316,9 +298,6 @@ class LogisticSigmoid(Builtin): >> LogisticSigmoid[{-0.2, 0.1, 0.3}] = {0.450166, 0.524979, 0.574443} - - #> LogisticSigmoid[I Pi] - = LogisticSigmoid[I Pi] """ summary_text = "logistic function" diff --git a/mathics/builtin/numbers/hyperbolic.py b/mathics/builtin/numbers/hyperbolic.py index 8f999077e..9884e619c 100644 --- a/mathics/builtin/numbers/hyperbolic.py +++ b/mathics/builtin/numbers/hyperbolic.py @@ -52,8 +52,6 @@ class ArcCosh(MPMathFunction): = 0. + 1.5708 I >> ArcCosh[0.00000000000000000000000000000000000000] = 1.5707963267948966192313216916397514421 I - #> ArcCosh[1.4] - = 0.867015 """ mpmath_name = "acosh" @@ -94,9 +92,6 @@ class ArcCoth(MPMathFunction): = 0. + 1.5708 I >> ArcCoth[0.5] = 0.549306 - 1.5708 I - - #> ArcCoth[0.000000000000000000000000000000000000000] - = 1.57079632679489661923132169163975144210 I """ summary_text = "inverse hyperbolic cotangent function" diff --git a/mathics/builtin/numbers/integer.py b/mathics/builtin/numbers/integer.py index e2a4c0714..0d9ce034d 100644 --- a/mathics/builtin/numbers/integer.py +++ b/mathics/builtin/numbers/integer.py @@ -256,10 +256,6 @@ class FromDigits(Builtin): = 0 >> FromDigits[""] = 0 - - #> FromDigits[x] - : The input must be a string of digits or a list. - = FromDigits[x, 10] """ summary_text = "integer from a list of digits" diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index 580ce0c35..77910941e 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -334,25 +334,15 @@ class RandomComplex(Builtin): >> RandomComplex[] = ... - #> 0 <= Re[%] <= 1 && 0 <= Im[%] <= 1 - = True >> RandomComplex[{1+I, 5+5I}] = ... - #> 1 <= Re[%] <= 5 && 1 <= Im[%] <= 5 - = True >> RandomComplex[1+I, 5] = {..., ..., ..., ..., ...} >> RandomComplex[{1+I, 2+2I}, {2, 2}] = {{..., ...}, {..., ...}} - - #> RandomComplex[{6, 2 Pi + I}] - = 6... - - #> RandomComplex[{6.3, 2.5 I}] // FullForm - = Complex[..., ...] """ messages = { @@ -463,8 +453,6 @@ class RandomInteger(Builtin): >> RandomInteger[{1, 5}] = ... - #> 1 <= % <= 5 - = True >> RandomInteger[100, {2, 3}] // TableForm = ... ... ... @@ -542,21 +530,8 @@ class RandomReal(Builtin): >> RandomReal[] = ... - #> 0 <= % <= 1 - = True - >> RandomReal[{1, 5}] = ... - - ## needs too much horizontal space in TeX form - #> RandomReal[100, {2, 3}] // TableForm - = ... ... ... - . - . ... ... ... - - #> RandomReal[{0, 1}, {1, -1}] - : The array dimensions {1, -1} given in position 2 of RandomReal[{0, 1}, {1, -1}] should be a list of non-negative machine-sized integers giving the dimensions for the result. - = RandomReal[{0, 1}, {1, -1}] """ messages = { @@ -691,10 +666,6 @@ class SeedRandom(Builtin): >> SeedRandom[] >> RandomInteger[100] = ... - - #> SeedRandom[x] - : Argument x should be an integer or string. - = SeedRandom[x] """ messages = { diff --git a/mathics/builtin/numbers/trig.py b/mathics/builtin/numbers/trig.py index 1218ae2b7..8ea925163 100644 --- a/mathics/builtin/numbers/trig.py +++ b/mathics/builtin/numbers/trig.py @@ -550,21 +550,6 @@ class ArcTan(MPMathFunction): >> ArcTan[1, 1] = Pi / 4 - #> ArcTan[-1, 1] - = 3 Pi / 4 - #> ArcTan[1, -1] - = -Pi / 4 - #> ArcTan[-1, -1] - = -3 Pi / 4 - - #> ArcTan[1, 0] - = 0 - #> ArcTan[-1, 0] - = Pi - #> ArcTan[0, 1] - = Pi / 2 - #> ArcTan[0, -1] - = -Pi / 2 """ mpmath_name = "atan" @@ -603,9 +588,6 @@ class Cos(MPMathFunction): >> Cos[3 Pi] = -1 - - #> Cos[1.5 Pi] - = -1.83697×10^-16 """ mpmath_name = "cos" @@ -815,9 +797,6 @@ class Sin(MPMathFunction): >> Plot[Sin[x], {x, -Pi, Pi}] = -Graphics- - - #> N[Sin[1], 40] - = 0.8414709848078965066525023216302989996226 """ mpmath_name = "sin" @@ -855,9 +834,6 @@ class Tan(MPMathFunction): = 0 >> Tan[Pi / 2] = ComplexInfinity - - #> Tan[0.5 Pi] - = 1.63312×10^16 """ mpmath_name = "tan" diff --git a/test/builtin/numbers/test_algebra.py b/test/builtin/numbers/test_algebra.py index 71f95cc4c..e1cbee3b3 100644 --- a/test/builtin/numbers/test_algebra.py +++ b/test/builtin/numbers/test_algebra.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ -Unit tests for mathics.builtins.numbers.algebra +Unit tests for mathics.builtins.numbers.algebra and +mathics.builtins.numbers.integer """ from test.helper import check_evaluation @@ -329,3 +330,183 @@ def test_fullsimplify(): ), ): check_evaluation(str_expr, str_expected, failure_message) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("Attributes[f] = {HoldAll}; Apart[f[x + x]]", None, "f[x + x]", None), + ("Attributes[f] = {}; Apart[f[x + x]]", None, "f[2 x]", None), + ## Errors: + ( + "Coefficient[x + y + 3]", + ("Coefficient called with 1 argument; 2 or 3 arguments are expected.",), + "Coefficient[3 + x + y]", + None, + ), + ( + "Coefficient[x + y + 3, 5]", + ("5 is not a valid variable.",), + "Coefficient[3 + x + y, 5]", + None, + ), + ## This is known bug of Sympy 1.0, next Sympy version will fix it by this commit + ## https://github.com/sympy/sympy/commit/25bf64b64d4d9a2dc563022818d29d06bc740d47 + ("Coefficient[x * y, z, 0]", None, "x y", "Sympy 1.0 retuns 0"), + ## TODO: Support Modulus + # ("Coefficient[(x + 2)^3 + (x + 3)^2, x, 0, {Modulus -> 3, Modulus -> 2, Modulus -> 10}]", + # None,"{2, 1, 7}", None), + ( + "CoefficientList[x + y, 5]", + ("5 is not a valid variable.",), + "CoefficientList[x + y, 5]", + None, + ), + ( + "CoefficientList[(x - 2 y)^4, {x, 2}]", + ("2 is not a valid variable.",), + "CoefficientList[(x - 2 y) ^ 4, {x, 2}]", + None, + ), + ( + "CoefficientList[x / y, {x, y}]", + ("x / y is not a polynomial.",), + "CoefficientList[x / y, {x, y}]", + None, + ), + ("Expand[x, Modulus -> -1] (* copy odd MMA behaviour *)", None, "0", None), + ( + "Expand[x, Modulus -> x]", + ("Value of option Modulus -> x should be an integer.",), + "Expand[x, Modulus -> x]", + None, + ), + ("a(b(c+d)+e) // Expand", None, "a b c + a b d + a e", None), + ("(y^2)^(1/2)/(2x+2y)//Expand", None, "Sqrt[y ^ 2] / (2 x + 2 y)", None), + ( + "2(3+2x)^2/(5+x^2+3x)^3 // Expand", + None, + "24 x / (5 + 3 x + x ^ 2) ^ 3 + 8 x ^ 2 / (5 + 3 x + x ^ 2) ^ 3 + 18 / (5 + 3 x + x ^ 2) ^ 3", + None, + ), + ## Modulus option + ( + "ExpandDenominator[1 / (x + y)^3, Modulus -> 3]", + None, + "1 / (x ^ 3 + y ^ 3)", + None, + ), + ( + "ExpandDenominator[1 / (x + y)^6, Modulus -> 4]", + None, + "1 / (x ^ 6 + 2 x ^ 5 y + 3 x ^ 4 y ^ 2 + 3 x ^ 2 y ^ 4 + 2 x y ^ 5 + y ^ 6)", + None, + ), + ( + "ExpandDenominator[2(3+2x)^2/(5+x^2+3x)^3]", + None, + "2 (3 + 2 x) ^ 2 / (125 + 225 x + 210 x ^ 2 + 117 x ^ 3 + 42 x ^ 4 + 9 x ^ 5 + x ^ 6)", + None, + ), + ## errors: + ( + "Exponent[x^2]", + ("Exponent called with 1 argument; 2 or 3 arguments are expected.",), + "Exponent[x ^ 2]", + None, + ), + ## Issue659 + ("Factor[{x+x^2}]", None, "{x (1 + x)}", None), + ("FactorTermsList[2 x^2 - 2, x]", None, "{2, 1, -1 + x ^ 2}", None), + ( + "MinimalPolynomial[7a, x]", + ("7 a is not an explicit algebraic number.",), + "MinimalPolynomial[7 a, x]", + None, + ), + ( + "MinimalPolynomial[3x^3 + 2x^2 + y^2 + ab, x]", + ("ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2 is not an explicit algebraic number.",), + "MinimalPolynomial[ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2, x]", + None, + ), + ## PurePoly + ("MinimalPolynomial[Sqrt[2 + Sqrt[3]]]", None, "1 - 4 #1 ^ 2 + #1 ^ 4", None), + ( + "PolynomialQ[x, x, y]", + ("PolynomialQ called with 3 arguments; 1 or 2 arguments are expected.",), + "PolynomialQ[x, x, y]", + None, + ), + ## Always return True if argument is Null + ( + "PolynomialQ[x^3 - 2 x/y + 3xz, ]", + None, + "True", + "Always return True if argument is Null", + ), + ( + "PolynomialQ[, {x, y, z}]", + None, + "True", + "True if the expression is Null", + ), + ( + "PolynomialQ[, ]", + None, + "True", + None, + ), + ## TODO: MMA and Sympy handle these cases differently + ## #> PolynomialQ[x^(1/2) + 6xyz] + ## : No variable is not supported in PolynomialQ. + ## = True + ## #> PolynomialQ[x^(1/2) + 6xyz, {}] + ## : No variable is not supported in PolynomialQ. + ## = True + ## #> PolynomialQ[x^3 - 2 x/y + 3xz] + ## : No variable is not supported in PolynomialQ. + ## = False + ## #> PolynomialQ[x^3 - 2 x/y + 3xz, {}] + ## : No variable is not supported in PolynomialQ. + ## = False + ("f[x]/x+f[x]/x^2//Together", None, "f[x] (1 + x) / x ^ 2", None), + ## failing test case from MMA docs + ("Variables[E^x]", None, "{}", None), + ], +) +def test_private_doctests_algebra(str_expr, msgs, str_expected, fail_msg): + """doctests for algebra""" + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "FromDigits[x]", + ("The input must be a string of digits or a list.",), + "FromDigits[x, 10]", + None, + ), + ], +) +def test_private_doctests_integer(str_expr, msgs, str_expected, fail_msg): + """doctests for integer""" + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/numbers/test_calculus.py b/test/builtin/numbers/test_calculus.py index 7d4d3c60c..0230a8c7e 100644 --- a/test/builtin/numbers/test_calculus.py +++ b/test/builtin/numbers/test_calculus.py @@ -2,7 +2,7 @@ """ Unit tests for mathics.builtins.numbers.calculus -In parituclar: +In partiuclar: FindRoot[], FindMinimum[], NFindMaximum[] tests @@ -226,3 +226,68 @@ def test_private_doctests_optimization(str_expr, msgs, str_expected, fail_msg): failure_message=fail_msg, expected_messages=msgs, ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "D[2/3 Cos[x] - 1/3 x Cos[x] Sin[x] ^ 2,x]//Expand", + None, + "-2 x Cos[x] ^ 2 Sin[x] / 3 + x Sin[x] ^ 3 / 3 - 2 Sin[x] / 3 - Cos[x] Sin[x] ^ 2 / 3", + None, + ), + ("D[f[#1], {#1,2}]", None, "f''[#1]", None), + ("D[(#1&)[t],{t,4}]", None, "0", None), + ("Attributes[f] ={HoldAll}; Apart[f''[x + x]]", None, "f''[2 x]", None), + ("Attributes[f] = {}; Apart[f''[x + x]]", None, "f''[2 x]", None), + ## Issue #375 + ("D[{#^2}, #]", None, "{2 #1}", None), + ("FindRoot[2.5==x,{x,0}]", None, "{x -> 2.5}", None), + ("DownValues[Integrate]", None, "{}", None), + ( + "Definition[Integrate]", + None, + ( + "Attributes[Integrate] = {Protected, ReadProtected}\n" + "\n" + "Options[Integrate] = {Assumptions -> $Assumptions, GenerateConditions -> Automatic, PrincipalValue -> False}\n" + ), + None, + ), + ( + "Integrate[Hold[x + x], {x, a, b}]", + None, + "Integrate[Hold[x + x], {x, a, b}]", + None, + ), + ("Integrate[sin[x], x]", None, "Integrate[sin[x], x]", None), + ("Integrate[x ^ 3.5 + x, x]", None, "x ^ 2 / 2 + 0.222222 x ^ 4.5", None), + ( + "Integrate[1/(x^5+1), x]", + None, + "RootSum[1 + 5 #1 + 25 #1 ^ 2 + 125 #1 ^ 3 + 625 #1 ^ 4&, Log[x + 5 #1] #1&] + Log[1 + x] / 5", + None, + ), + ("Integrate[ArcTan(x), x]", None, "x ^ 2 ArcTan / 2", None), + ("Integrate[E[x], x]", None, "Integrate[E[x], x]", None), + ("Integrate[Exp[-(x/2)^2],{x,-Infinity,+Infinity}]", None, "2 Sqrt[Pi]", None), + ( + "Integrate[Exp[-1/(x^2)], x]", + None, + "x E ^ (-1 / x ^ 2) + Sqrt[Pi] Erf[1 / x]", + None, + ), + ], +) +def test_private_doctests_calculus(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/numbers/test_hyperbolic.py b/test/builtin/numbers/test_hyperbolic.py index 762cab81f..5b7a4cc31 100644 --- a/test/builtin/numbers/test_hyperbolic.py +++ b/test/builtin/numbers/test_hyperbolic.py @@ -1,12 +1,15 @@ -# -*- coding: utf-8 -*- +## -*- coding: utf-8 -*- """ -Unit tests for mathics.builtins.numbers.hyperbolic +Unit tests for mathics.builtins.numbers.hyperbolic and +mathics.builtins.numbers.exp These simple verify various rules from from symja_android_library/symja_android_library/rules/Gudermannian.m """ from test.helper import check_evaluation +import pytest + def test_gudermannian(): for str_expr, str_expected in ( @@ -34,3 +37,53 @@ def test_complexexpand(): ), ): check_evaluation(str_expr, str_expected) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("ArcCosh[1.4]", None, "0.867015", None), + ( + "ArcCoth[0.000000000000000000000000000000000000000]", + None, + "1.57079632679489661923132169163975144210 I", + None, + ), + ], +) +def test_private_doctests_hyperbolic(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("Exp[1.*^20]", ("Overflow occurred in computation.",), "Overflow[]", None), + ("Log[1000] / Log[10] // Simplify", None, "3", None), + ("Log[1.4]", None, "0.336472", None), + ("Log[Exp[1.4]]", None, "1.4", None), + ("Log[-1.4]", None, "0.336472 + 3.14159 I", None), + ("N[Log[10], 30]", None, "2.30258509299404568401799145468", None), + ("LogisticSigmoid[I Pi]", None, "LogisticSigmoid[I Pi]", None), + ], +) +def test_private_doctests_exp(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/numbers/test_randomnumbers.py b/test/builtin/numbers/test_randomnumbers.py index d2bf37277..9e0e7bccd 100644 --- a/test/builtin/numbers/test_randomnumbers.py +++ b/test/builtin/numbers/test_randomnumbers.py @@ -39,3 +39,70 @@ def test_random_sample(str_expr, str_expected): to_string_expr=True, to_string_expected=True, ) + + +# -*- coding: utf-8 -*- +""" +Unit tests for mathics.builtins.specialfns.gamma +""" +from test.helper import check_evaluation + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "RandomComplex[] //(0 <= Re[#1] <= 1 && 0 <= Im[#1] <= 1)&", + None, + "True", + None, + ), + ( + "z=RandomComplex[{1+I, 5+5I}];1 <= Re[z] <= 5 && 1 <= Im[z] <= 5", + None, + "True", + None, + ), + ( + "z=.;RandomComplex[{6.3, 2.5 I}] // Head", + None, + "Complex", + None, + ), + ("RandomInteger[{1, 5}]// (1<= #1 <= 5)&", None, "True", None), + ("RandomReal[]// (0<= #1 <= 1)&", None, "True", None), + ( + "Length /@ RandomReal[100, {2, 3}]", + None, + "{3, 3}", + None, + ), + ( + "RandomReal[{0, 1}, {1, -1}]", + ( + "The array dimensions {1, -1} given in position 2 of RandomReal[{0, 1}, {1, -1}] should be a list of non-negative machine-sized integers giving the dimensions for the result.", + ), + "RandomReal[{0, 1}, {1, -1}]", + None, + ), + ( + "SeedRandom[x]", + ("Argument x should be an integer or string.",), + "SeedRandom[x]", + None, + ), + ], +) +def test_private_doctests_randomnumbers(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/numbers/test_trig.py b/test/builtin/numbers/test_trig.py index 5aee15cb7..dbe01c2d4 100644 --- a/test/builtin/numbers/test_trig.py +++ b/test/builtin/numbers/test_trig.py @@ -7,6 +7,8 @@ """ from test.helper import check_evaluation +import pytest + def test_ArcCos(): for str_expr, str_expected in ( @@ -22,3 +24,31 @@ def test_ArcCos(): ("ArcCos[(1 + Sqrt[3]) / (2*Sqrt[2])]", "1/12 Pi"), ): check_evaluation(str_expr, str_expected) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("ArcTan[-1, 1]", None, "3 Pi / 4", None), + ("ArcTan[1, -1]", None, "-Pi / 4", None), + ("ArcTan[-1, -1]", None, "-3 Pi / 4", None), + ("ArcTan[1, 0]", None, "0", None), + ("ArcTan[-1, 0]", None, "Pi", None), + ("ArcTan[0, 1]", None, "Pi / 2", None), + ("ArcTan[0, -1]", None, "-Pi / 2", None), + ("Cos[1.5 Pi]", None, "-1.83697×10^-16", None), + ("N[Sin[1], 40]", None, "0.8414709848078965066525023216302989996226", None), + ("Tan[0.5 Pi]", None, "1.63312×10^16", None), + ], +) +def test_private_doctests_trig(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/specialfns/test_bessel.py b/test/builtin/specialfns/test_bessel.py index 6b7296763..77a73ee98 100644 --- a/test/builtin/specialfns/test_bessel.py +++ b/test/builtin/specialfns/test_bessel.py @@ -14,7 +14,7 @@ # by SymPy. [ ( - "BesselI[1/2,z]", + "z=.;BesselI[1/2,z]", "Sqrt[2] Sinh[z] / (Sqrt[z] Sqrt[Pi])", "BesselI 1/2 rule", ), diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py index 293a6675d..475e3bff6 100644 --- a/test/core/parser/test_parser.py +++ b/test/core/parser/test_parser.py @@ -282,6 +282,8 @@ def testDerivative(self): self.check("f'", "Derivative[1][f]") self.check("f''", "Derivative[2][f]") self.check("f' '", "Derivative[2][f]") + self.check("f '' ''", "Derivative[4][f]") + self.check("Derivative[x][4] '", "Derivative[1][Derivative[x][4]]") def testPlus(self): self.check("+1", Node("Plus", Number("1"))) From 0d0ba213af64c3f1539bea1d76c8b69d68e9ab6c Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 9 Sep 2023 23:20:51 -0300 Subject: [PATCH 07/51] missing pytest --- test/builtin/numbers/test_diffeqns.py | 106 ++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 test/builtin/numbers/test_diffeqns.py diff --git a/test/builtin/numbers/test_diffeqns.py b/test/builtin/numbers/test_diffeqns.py new file mode 100644 index 000000000..46a8579d7 --- /dev/null +++ b/test/builtin/numbers/test_diffeqns.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for mathics.builtins.numbers.diffeqns +""" +from test.helper import check_evaluation + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ## FIXME: sympy solves this as `Function[{x}, C[1] + Integrate[ArcSin[f[2 x]], x]]` + # ( + # "Attributes[f] = {HoldAll}; DSolve[f[x + x] == Sin[f'[x]], f, x]", + # ( + # ( + # "To avoid possible ambiguity, the arguments of the dependent " + # "variable in f[x + x] == Sin[f'[x]] should literally match " + # "the independent variables." + # ), + # ), + # "DSolve[f[x + x] == Sin[f'[x]], f, x]", + # "sympy solves this as `Function[{x}, C[1] + Integrate[ArcSin[f[2 x]], x]]`", + # ), + # """ + # ( + # "Attributes[f] = {}; DSolve[f[x + x] == Sin[f'[x]], f, x]", + # ( + # ( + # "To avoid possible ambiguity, the arguments of the dependent " + # "variable in f[2 x] == Sin[f'[x]] should literally match " + # "the independent variables." + # ), + # ), + # "DSolve[f[2 x] == Sin[f'[x]], f, x]", + # None, + # ), + ( + "DSolve[f'[x] == f[x], f, x] // FullForm", + None, + "{{Rule[f, Function[{x}, Times[C[1], Power[E, x]]]]}}", + None, + ), + ( + "DSolve[f'[x] == f[x], f, x] /. {C[1] -> 1}", + None, + "{{f -> (Function[{x}, 1 E ^ x])}}", + None, + ), + ( + "DSolve[f'[x] == f[x], f, x] /. {C -> D}", + None, + "{{f -> (Function[{x}, D[1] E ^ x])}}", + None, + ), + ( + "DSolve[f'[x] == f[x], f, x] /. {C[1] -> C[0]}", + None, + "{{f -> (Function[{x}, C[0] E ^ x])}}", + None, + ), + ( + "DSolve[f[x] == 0, f, {}]", + ("{} cannot be used as a variable.",), + "DSolve[f[x] == 0, f, {}]", + None, + ), + ## Order of arguments shoudn't matter + ( + "DSolve[D[f[x, y], x] == D[f[x, y], y], f, {x, y}]", + None, + "{{f -> (Function[{x, y}, C[1][-x - y]])}}", + None, + ), + ( + "DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {x, y}]", + None, + "{{f[x, y] -> C[1][-x - y]}}", + None, + ), + ( + "DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {y, x}]", + None, + "{{f[x, y] -> C[1][-x - y]}}", + None, + ), + ( + "DSolve[\\[Gamma]'[x] == 0, \\[Gamma], x]", + None, + "{{γ -> (Function[{x}, C[1]])}}", + "sympy #11669 test", + ), + ], +) +def test_private_doctests_diffeqns(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From 8a8cef75ebd31b96b2d370796208b9f0dda40cc3 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 10 Sep 2023 00:20:22 -0300 Subject: [PATCH 08/51] move private doctests to pytest in mathics.builtin.numeric and mathics.builtin.intfns --- mathics/builtin/intfns/combinatorial.py | 86 +--------- mathics/builtin/intfns/divlike.py | 28 ---- mathics/builtin/intfns/recurrence.py | 3 - mathics/builtin/numeric.py | 39 +---- test/builtin/test_intfns.py | 213 ++++++++++++++++++++++++ test/builtin/test_numeric.py | 73 +++++++- 6 files changed, 289 insertions(+), 153 deletions(-) create mode 100644 test/builtin/test_intfns.py diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index 681f9277d..22a77f486 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -113,10 +113,6 @@ class Binomial(MPMathFunction): = 0 >> Binomial[-10.5, -3.5] = 0. - - ## TODO should be ComplexInfinity but mpmath returns +inf - #> Binomial[-10, -3.5] - = Infinity """ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED @@ -411,90 +407,10 @@ class Subsets(Builtin): The odd-numbered subsets of {a,b,c,d} in reverse order: >> Subsets[{a, b, c, d}, All, {15, 1, -2}] = {{b, c, d}, {a, b, d}, {c, d}, {b, c}, {a, c}, {d}, {b}, {}} - - #> Subsets[{}] - = {{}} - - #> Subsets[] - = Subsets[] - - #> Subsets[{a, b, c}, 2.5] - : Position 2 of Subsets[{a, b, c}, 2.5] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, 2.5] - - #> Subsets[{a, b, c}, -1] - : Position 2 of Subsets[{a, b, c}, -1] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, -1] - - #> Subsets[{a, b, c}, {3, 4, 5, 6}] - : Position 2 of Subsets[{a, b, c}, {3, 4, 5, 6}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, {3, 4, 5, 6}] - - #> Subsets[{a, b, c}, {-1, 2}] - : Position 2 of Subsets[{a, b, c}, {-1, 2}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, {-1, 2}] - - #> Subsets[{a, b, c}, All] - = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} - - #> Subsets[{a, b, c}, Infinity] - = {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} - - #> Subsets[{a, b, c}, ALL] - : Position 2 of Subsets[{a, b, c}, ALL] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, ALL] - - #> Subsets[{a, b, c}, {a}] - : Position 2 of Subsets[{a, b, c}, {a}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, {a}] - - #> Subsets[{a, b, c}, {}] - : Position 2 of Subsets[{a, b, c}, {}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{a, b, c}, {}] - - #> Subsets[{a, b}, 0] - = {{}} - - #> Subsets[{1, 2}, x] - : Position 2 of Subsets[{1, 2}, x] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer - = Subsets[{1, 2}, x] - - #> Subsets[x] - : Nonatomic expression expected at position 1 in Subsets[x]. - = Subsets[x] - - #> Subsets[x, {1, 2}] - : Nonatomic expression expected at position 1 in Subsets[x, {1, 2}]. - = Subsets[x, {1, 2}] - - #> Subsets[x, {1, 2, 3}, {1, 3}] - : Nonatomic expression expected at position 1 in Subsets[x, {1, 2, 3}, {1, 3}]. - = Subsets[x, {1, 2, 3}, {1, 3}] - - #> Subsets[a + b + c] - = {0, a, b, c, a + b, a + c, b + c, a + b + c} - - #> Subsets[f[a, b, c]] - = {f[], f[a], f[b], f[c], f[a, b], f[a, c], f[b, c], f[a, b, c]} - - #> Subsets[a + b + c, {1, 3, 2}] - = {a, b, c, a + b + c} - - #> Subsets[a* b * c, All, {6}] - = {a c} - - #> Subsets[{a, b, c}, {1, Infinity}] - = {{a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}} - - #> Subsets[{a, b, c}, {1, Infinity, 2}] - = {{a}, {b}, {c}, {a, b, c}} - - #> Subsets[{a, b, c}, {3, Infinity, -1}] - = {} """ messages = { - "nninfseq": "Position 2 of `1` must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer", + "nninfseq": "Position 2 of `1` must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", "normal": "Nonatomic expression expected at position 1 in `1`.", } diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 864558707..b68dfd5ea 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -298,16 +298,6 @@ class Quotient(Builtin): >> Quotient[23, 7] = 3 - - #> Quotient[13, 0] - : Infinite expression Quotient[13, 0] encountered. - = ComplexInfinity - #> Quotient[-17, 7] - = -3 - #> Quotient[-17, -4] - = 4 - #> Quotient[19, -4] - = -5 """ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED @@ -338,24 +328,6 @@ class QuotientRemainder(Builtin): >> QuotientRemainder[23, 7] = {3, 2} - - #> QuotientRemainder[13, 0] - : The argument 0 in QuotientRemainder[13, 0] should be nonzero. - = QuotientRemainder[13, 0] - #> QuotientRemainder[-17, 7] - = {-3, 4} - #> QuotientRemainder[-17, -4] - = {4, -1} - #> QuotientRemainder[19, -4] - = {-5, -1} - #> QuotientRemainder[a, 0] - = QuotientRemainder[a, 0] - #> QuotientRemainder[a, b] - = QuotientRemainder[a, b] - #> QuotientRemainder[5.2,2.5] - = {2, 0.2} - #> QuotientRemainder[5, 2.] - = {2, 1.} """ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED diff --git a/mathics/builtin/intfns/recurrence.py b/mathics/builtin/intfns/recurrence.py index aaf544c36..c28637069 100644 --- a/mathics/builtin/intfns/recurrence.py +++ b/mathics/builtin/intfns/recurrence.py @@ -63,9 +63,6 @@ class HarmonicNumber(MPMathFunction): >> HarmonicNumber[3.8] = 2.03806 - - #> HarmonicNumber[-1.5] - = 0.613706 """ rules = { diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 44c8999ea..63bf5d88e 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -257,21 +257,6 @@ class N(Builtin): = F[3.14159265358979300000000000000] >> N[F[Pi], 30, Method->"sympy"] = F[3.14159265358979323846264338328] - #> p=N[Pi,100] - = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 - #> ToString[p] - = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 - - #> N[1.012345678901234567890123, 20] - = 1.0123456789012345679 - - #> N[I, 30] - = 1.00000000000000000000000000000 I - - #> N[1.012345678901234567890123, 50] - = 1.01234567890123456789012 - #> % // Precision - = 24. """ options = {"Method": "Automatic"} @@ -454,18 +439,6 @@ class Rationalize(Builtin): Find the exact rational representation of 'N[Pi]' >> Rationalize[N[Pi], 0] = 245850922 / 78256779 - - #> Rationalize[N[Pi] + 0.8 I, x] - : Tolerance specification x must be a non-negative number. - = Rationalize[3.14159 + 0.8 I, x] - - #> Rationalize[N[Pi] + 0.8 I, -1] - : Tolerance specification -1 must be a non-negative number. - = Rationalize[3.14159 + 0.8 I, -1] - - #> Rationalize[x, y] - : Tolerance specification y must be a non-negative number. - = Rationalize[x, y] """ messages = { @@ -769,17 +742,11 @@ class Sign(SympyFunction): = 0 >> Sign[{-5, -10, 15, 20, 0}] = {-1, -1, 1, 1, 0} - #> Sign[{1, 2.3, 4/5, {-6.7, 0}, {8/9, -10}}] - = {1, 1, 1, {-1, 0}, {1, -1}} + + For a complex number, 'Sign' returns the phase of the number: >> Sign[3 - 4*I] = 3 / 5 - 4 I / 5 - #> Sign[1 - 4*I] == (1/17 - 4 I/17) Sqrt[17] - = True - #> Sign[4, 5, 6] - : Sign called with 3 arguments; 1 argument is expected. - = Sign[4, 5, 6] - #> Sign["20"] - = Sign[20] + """ summary_text = "complex sign of a number" diff --git a/test/builtin/test_intfns.py b/test/builtin/test_intfns.py new file mode 100644 index 000000000..c7c790728 --- /dev/null +++ b/test/builtin/test_intfns.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.intfns +""" + +from test.helper import check_evaluation, session + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("HarmonicNumber[-1.5]", None, "0.613706", None), + ], +) +def test_private_doctests_recurrence(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ## TODO should be ComplexInfinity but mpmath returns +inf + ("Binomial[-10, -3.5]", None, "Infinity", None), + ("Subsets[{}]", None, "{{}}", None), + ("Subsets[]", None, "Subsets[]", None), + ( + "Subsets[{a, b, c}, 2.5]", + ( + "Position 2 of Subsets[{a, b, c}, 2.5] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, 2.5]", + None, + ), + ( + "Subsets[{a, b, c}, -1]", + ( + "Position 2 of Subsets[{a, b, c}, -1] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, -1]", + None, + ), + ( + "Subsets[{a, b, c}, {3, 4, 5, 6}]", + ( + "Position 2 of Subsets[{a, b, c}, {3, 4, 5, 6}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, {3, 4, 5, 6}]", + None, + ), + ( + "Subsets[{a, b, c}, {-1, 2}]", + ( + "Position 2 of Subsets[{a, b, c}, {-1, 2}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, {-1, 2}]", + None, + ), + ( + "Subsets[{a, b, c}, All]", + None, + "{{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}", + None, + ), + ( + "Subsets[{a, b, c}, Infinity]", + None, + "{{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}", + None, + ), + ( + "Subsets[{a, b, c}, ALL]", + ( + "Position 2 of Subsets[{a, b, c}, ALL] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, ALL]", + None, + ), + ( + "Subsets[{a, b, c}, {a}]", + ( + "Position 2 of Subsets[{a, b, c}, {a}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, {a}]", + None, + ), + ( + "Subsets[{a, b, c}, {}]", + ( + "Position 2 of Subsets[{a, b, c}, {}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{a, b, c}, {}]", + None, + ), + ("Subsets[{a, b}, 0]", None, "{{}}", None), + ( + "Subsets[{1, 2}, x]", + ( + "Position 2 of Subsets[{1, 2}, x] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.", + ), + "Subsets[{1, 2}, x]", + None, + ), + ( + "Subsets[x]", + ("Nonatomic expression expected at position 1 in Subsets[x].",), + "Subsets[x]", + None, + ), + ( + "Subsets[x, {1, 2}]", + ("Nonatomic expression expected at position 1 in Subsets[x, {1, 2}].",), + "Subsets[x, {1, 2}]", + None, + ), + ( + "Subsets[x, {1, 2, 3}, {1, 3}]", + ( + "Nonatomic expression expected at position 1 in Subsets[x, {1, 2, 3}, {1, 3}].", + ), + "Subsets[x, {1, 2, 3}, {1, 3}]", + None, + ), + ( + "Subsets[a + b + c]", + None, + "{0, a, b, c, a + b, a + c, b + c, a + b + c}", + None, + ), + ( + "Subsets[f[a, b, c]]", + None, + "{f[], f[a], f[b], f[c], f[a, b], f[a, c], f[b, c], f[a, b, c]}", + None, + ), + ("Subsets[a + b + c, {1, 3, 2}]", None, "{a, b, c, a + b + c}", None), + ("Subsets[a* b * c, All, {6}]", None, "{a c}", None), + ( + "Subsets[{a, b, c}, {1, Infinity}]", + None, + "{{a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}", + None, + ), + ( + "Subsets[{a, b, c}, {1, Infinity, 2}]", + None, + "{{a}, {b}, {c}, {a, b, c}}", + None, + ), + ("Subsets[{a, b, c}, {3, Infinity, -1}]", None, "{}", None), + ], +) +def test_private_doctests_combinatorial(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "Quotient[13, 0]", + ("Infinite expression Quotient[13, 0] encountered.",), + "ComplexInfinity", + None, + ), + ("Quotient[-17, 7]", None, "-3", None), + ("Quotient[-17, -4]", None, "4", None), + ("Quotient[19, -4]", None, "-5", None), + ( + "QuotientRemainder[13, 0]", + ("The argument 0 in QuotientRemainder[13, 0] should be nonzero.",), + "QuotientRemainder[13, 0]", + None, + ), + ("QuotientRemainder[-17, 7]", None, "{-3, 4}", None), + ("QuotientRemainder[-17, -4]", None, "{4, -1}", None), + ("QuotientRemainder[19, -4]", None, "{-5, -1}", None), + ("QuotientRemainder[a, 0]", None, "QuotientRemainder[a, 0]", None), + ("QuotientRemainder[a, b]", None, "QuotientRemainder[a, b]", None), + ("QuotientRemainder[5.2,2.5]", None, "{2, 0.2}", None), + ("QuotientRemainder[5, 2.]", None, "{2, 1.}", None), + ], +) +def test_private_doctests_divlike(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/builtin/test_numeric.py b/test/builtin/test_numeric.py index dff0d72b9..ae5e6c603 100644 --- a/test/builtin/test_numeric.py +++ b/test/builtin/test_numeric.py @@ -4,9 +4,10 @@ In particular, Rationalize and RealValuNumberQ """ - from test.helper import check_evaluation +import pytest + def test_rationalize(): # Some of the Rationalize tests were taken from Symja's tests and docs @@ -67,3 +68,73 @@ def test_realvalued(): ), ): check_evaluation(str_expr, str_expected) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ( + "p=N[Pi,100]", + None, + "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068", + None, + ), + ( + "ToString[p]", + None, + "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068", + None, + ), + ("N[1.012345678901234567890123, 20]", None, "1.0123456789012345679", None), + ("N[I, 30]", None, "1.00000000000000000000000000000 I", None), + ( + "N[1.012345678901234567890123, 50] //{#1, #1//Precision}&", + None, + "{1.01234567890123456789012, 24.}", + None, + ), + ( + "p=.;x=.;y=.;Rationalize[N[Pi] + 0.8 I, x]", + ("Tolerance specification x must be a non-negative number.",), + "Rationalize[3.14159 + 0.8 I, x]", + None, + ), + ( + "Rationalize[N[Pi] + 0.8 I, -1]", + ("Tolerance specification -1 must be a non-negative number.",), + "Rationalize[3.14159 + 0.8 I, -1]", + None, + ), + ( + "Rationalize[x, y]", + ("Tolerance specification y must be a non-negative number.",), + "Rationalize[x, y]", + None, + ), + ( + "Sign[{1, 2.3, 4/5, {-6.7, 0}, {8/9, -10}}]", + None, + "{1, 1, 1, {-1, 0}, {1, -1}}", + None, + ), + ("Sign[1 - 4*I] == (1/17 - 4 I/17) Sqrt[17]", None, "True", None), + ( + "Sign[4, 5, 6]", + ("Sign called with 3 arguments; 1 argument is expected.",), + "Sign[4, 5, 6]", + None, + ), + ('Sign["20"]', None, "Sign[20]", None), + ], +) +def test_private_doctests_numeric(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From e90769d3388962e7076231413a8e43b3a17bdf66 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 10 Sep 2023 01:41:01 -0300 Subject: [PATCH 09/51] arithmetic --- mathics/builtin/arithmetic.py | 8 ------- test/builtin/arithmetic/test_basic.py | 32 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 0f5a329cc..5f622282c 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -958,14 +958,6 @@ class Sum(IterationFunction, SympyFunction): Verify algebraic identities: >> Sum[x ^ 2, {x, 1, y}] - y * (y + 1) * (2 * y + 1) / 6 = 0 - - - ## Issue #302 - ## The sum should not converge since the first term is 1/0. - #> Sum[i / Log[i], {i, 1, Infinity}] - = Sum[i / Log[i], {i, 1, Infinity}] - #> Sum[Cos[Pi i], {i, 1, Infinity}] - = Sum[Cos[i Pi], {i, 1, Infinity}] """ summary_text = "discrete sum" diff --git a/test/builtin/arithmetic/test_basic.py b/test/builtin/arithmetic/test_basic.py index 1bec99de7..07e83f1be 100644 --- a/test/builtin/arithmetic/test_basic.py +++ b/test/builtin/arithmetic/test_basic.py @@ -449,3 +449,35 @@ def test_cuberoot(str_expr, str_expected, msgs, failmsg): check_evaluation( str_expr, str_expected, expected_messages=msgs, failure_message=failmsg ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ## Issue #302 + ## The sum should not converge since the first term is 1/0. + ( + "Sum[i / Log[i], {i, 1, Infinity}]", + None, + "Sum[i / Log[i], {i, 1, Infinity}]", + None, + ), + ( + "Sum[Cos[Pi i], {i, 1, Infinity}]", + None, + "Sum[Cos[i Pi], {i, 1, Infinity}]", + None, + ), + ], +) +def test_private_doctests_arithmetic(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From 42be67814654b146f72eb60ea91426fb94bcb6e0 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 14 Nov 2023 18:03:32 -0500 Subject: [PATCH 10/51] Lint file --- mathics/core/symbols.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index adfe063e9..fca9cbd27 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import time -from typing import Any, FrozenSet, List, Optional, Tuple +from typing import Any, FrozenSet, List, Optional from mathics.core.element import ( BaseElement, @@ -17,6 +17,7 @@ sympy_symbol_prefix = "_Mathics_User_" sympy_slot_prefix = "_Mathics_Slot_" + # FIXME: This is repeated below class NumericOperators: """ @@ -199,7 +200,8 @@ class Atom(BaseElement): Atom is not a directly-mentioned WL entity, although conceptually it very much seems to exist. - The other kinds expression element is a Builtin, e.g. `ByteArray``, `CompiledCode`` or ``Image``. + The other kinds expression element is a Builtin, e.g. `ByteArray``, `CompiledCode`` + or ``Image``. """ _head_name = "" @@ -251,8 +253,8 @@ def get_atom_name(self) -> str: def get_atoms(self, include_heads=True) -> List["Atom"]: return [self] - # We seem to need this because the caller doesn't distinguish something with elements - # from a single atom. + # We seem to need this because the caller doesn't distinguish + # something with elements from a single atom. def get_elements(self): return [] @@ -262,14 +264,18 @@ def get_head(self) -> "Symbol": def get_head_name(self) -> "str": return self.class_head_name # System`" + self.__class__.__name__ - # def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True): + # def get_option_values(self, evaluation, allow_symbols=False, + # stop_on_error=True): # """ # Build a dictionary of options from an expression. - # For example Symbol("Integrate").get_option_values(evaluation, allow_symbols=True) - # will return a list of options associated to the definition of the symbol "Integrate". + # For example Symbol("Integrate").get_option_values(evaluation, + # allow_symbols=True) + # will return a list of options associated to the definition of the symbol + # "Integrate". # If self is not an expression, # """ - # print("get_option_values is trivial for ", (self, stop_on_error, allow_symbols )) + # print("get_option_values is trivial for ", (self, stop_on_error, + # allow_symbols )) # 1/0 # return None if stop_on_error else {} @@ -661,7 +667,6 @@ class SymbolConstant(Symbol): # We use __new__ here to unsure that two Integer's that have the same value # return the same object. def __new__(cls, name, value): - name = ensure_context(name) self = cls._symbol_constants.get(name) if self is None: From b7ebdb31672f84df65e886df3ca9052809ff3756 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:19:55 +0800 Subject: [PATCH 11/51] Update tensors.py Add LeviCivitaTensor --- mathics/builtin/tensors.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index d58c86bff..b991dc9c2 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -490,3 +490,43 @@ def eval(self, m, evaluation: Evaluation): else: result[col_index].append(item) return ListExpression(*[ListExpression(*row) for row in result]) + + +class LeviCivitaTensor(Builtin): + """ + :Levi-Civita tensor:https://en.wikipedia.org/wiki/Levi-Civita_symbol \ + (:WMA link:https://reference.wolfram.com/language/ref/LeviCivitaTensor.html) + +
    +
    'LeviCivitaTensor[$d$]' +
    gives the $d$-dimensional Levi-Civita totally antisymmetric tensor. +
    + + >> LeviCivitaTensor[3] + = SparseArray[Automatic, {3, 3, 3}, 0, {{1, 2, 3} → 1, {1, 3, 2} → -1, {2, 1, 3} → -1, {2, 3, 1} → 1, {3, 1, 2} → 1, {3, 2, 1} → -1}] + + >> LeviCivitaTensor[3, List] + = {{{0, 0, 0}, {0, 0, 1}, {0, -1, 0}}, {{0, 0, -1}, {0, 0, 0}, {1, 0, 0}}, {{0, 1, 0}, {-1, 0, 0}, {0, 0, 0}}} + """ + + rules = { + "LeviCivitaTensor[d_Integer]/; Greater[d, 0]": "LeviCivitaTensor[d, SparseArray]", + "LeviCivitaTensor[d_Integer, List] /; Greater[d, 0]": "LeviCivitaTensor[d, SparseArray] // Normal", + } + + summary_text = "give the Levi-Civita tensor with a given dimension" + + def eval(self, d, type, evaluation: Evaluation): + "LeviCivitaTensor[d_Integer, type_]" + + from mathics.core.systemsymbols import SymbolSparseArray, SymbolRule + from mathics.core.convert.python import from_python + from sympy.utilities.iterables import permutations + from sympy.combinatorics import Permutation + + if isinstance(d, Integer) and type == SymbolSparseArray: + d = d.get_int_value() + perms = list(permutations([i for i in range(1, d + 1)])) + rules = [Expression(SymbolRule, from_python(p), from_python(Permutation.from_sequence(p).signature())) for p in perms] + return Expression(SymbolSparseArray, from_python(rules), from_python([d] * d)) + From 60b9ee7c8f9f489dbd266191c0fc76f3525bd0e1 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:57:14 +0800 Subject: [PATCH 12/51] Update CHANGES.rst --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8e602d47c..dd7fa794c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,7 @@ New Builtins * `Elements` +* `LeviCivitaTensor` * `RealAbs` and `RealSign` * `RealValuedNumberQ` From 2da36b455ccf72c0af0b788bf3789975af7cf89b Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:03:28 +0800 Subject: [PATCH 13/51] Update tensors.py --- mathics/builtin/tensors.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index b991dc9c2..afef4af4c 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -22,11 +22,15 @@ from mathics.core.atoms import Integer, String from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED from mathics.core.builtin import BinaryOperator, Builtin +from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue +from mathics.core.systemsymbols import SymbolRule, SymbolSparseArray from mathics.eval.parts import get_part +from sympy.combinatorics import Permutation +from sympy.utilities.iterables import permutations def get_default_distance(p): @@ -519,11 +523,6 @@ class LeviCivitaTensor(Builtin): def eval(self, d, type, evaluation: Evaluation): "LeviCivitaTensor[d_Integer, type_]" - from mathics.core.systemsymbols import SymbolSparseArray, SymbolRule - from mathics.core.convert.python import from_python - from sympy.utilities.iterables import permutations - from sympy.combinatorics import Permutation - if isinstance(d, Integer) and type == SymbolSparseArray: d = d.get_int_value() perms = list(permutations([i for i in range(1, d + 1)])) From 75811ba543d0143ee955b0fb53f9f4138ff652ba Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:07:48 +0800 Subject: [PATCH 14/51] Update tensors.py --- mathics/builtin/tensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index afef4af4c..3dd702a13 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -507,7 +507,7 @@ class LeviCivitaTensor(Builtin): >> LeviCivitaTensor[3] - = SparseArray[Automatic, {3, 3, 3}, 0, {{1, 2, 3} → 1, {1, 3, 2} → -1, {2, 1, 3} → -1, {2, 3, 1} → 1, {3, 1, 2} → 1, {3, 2, 1} → -1}] + = SparseArray[Automatic, {3, 3, 3}, 0, {{1, 2, 3} -> 1, {1, 3, 2} -> -1, {2, 1, 3} -> -1, {2, 3, 1} -> 1, {3, 1, 2} -> 1, {3, 2, 1} -> -1}] >> LeviCivitaTensor[3, List] = {{{0, 0, 0}, {0, 0, 1}, {0, -1, 0}}, {{0, 0, -1}, {0, 0, 0}, {1, 0, 0}}, {{0, 1, 0}, {-1, 0, 0}, {0, 0, 0}}} From 6a0be61dafccf3449118256f1d40958a679e6dcd Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:09:56 +0800 Subject: [PATCH 15/51] Update CHANGES.rst --- CHANGES.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index dd7fa794c..c9b22ebaf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,10 +8,10 @@ New Builtins ++++++++++++ -* `Elements` -* `LeviCivitaTensor` -* `RealAbs` and `RealSign` -* `RealValuedNumberQ` +* ``Elements`` +* ``LeviCivitaTensor`` +* ``RealAbs`` and ``RealSign`` +* ``RealValuedNumberQ`` Compatibility From f779a44a616bf459b11f1b3ef1e40de487df7e9c Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:36:29 +0800 Subject: [PATCH 16/51] Update tensors.py --- mathics/builtin/tensors.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 3dd702a13..53fc79286 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -19,6 +19,9 @@ """ +from sympy.combinatorics import Permutation +from sympy.utilities.iterables import permutations + from mathics.core.atoms import Integer, String from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED from mathics.core.builtin import BinaryOperator, Builtin @@ -29,8 +32,6 @@ from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolRule, SymbolSparseArray from mathics.eval.parts import get_part -from sympy.combinatorics import Permutation -from sympy.utilities.iterables import permutations def get_default_distance(p): From 3500cab40ab82ff667eba0a21291037aa0734cc9 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:17:33 +0800 Subject: [PATCH 17/51] Update tensors.py never used isort or black before :( Hope it passes the checks this time. --- mathics/builtin/tensors.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 53fc79286..f084cdd26 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -527,6 +527,14 @@ def eval(self, d, type, evaluation: Evaluation): if isinstance(d, Integer) and type == SymbolSparseArray: d = d.get_int_value() perms = list(permutations([i for i in range(1, d + 1)])) - rules = [Expression(SymbolRule, from_python(p), from_python(Permutation.from_sequence(p).signature())) for p in perms] - return Expression(SymbolSparseArray, from_python(rules), from_python([d] * d)) - + rules = [ + Expression( + SymbolRule, + from_python(p), + from_python(Permutation.from_sequence(p).signature()), + ) + for p in perms + ] + return Expression( + SymbolSparseArray, from_python(rules), from_python([d] * d) + ) From b8eb3443bd0d198d0b82d93f7d466677bd912772 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 16 Nov 2023 07:10:21 -0500 Subject: [PATCH 18/51] Add Li Xiang --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 9dc803a42..206990c35 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -36,6 +36,7 @@ Additional contributions were made by: - Pablo Emilio Escobar Gaviria @GarkGarcia - Rocky Bernstein @rocky - Tiago Cavalcante Trindade @TiagoCavalcante +- Li Xiang @Li-Xiang-Ideal Thanks to the authors of all projects that are used in Mathics: - Django From cc131eb4578292fdadcca0a53dc5eab37f873b96 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 19 Nov 2023 12:19:49 -0500 Subject: [PATCH 19/51] Administrivia: Correct Downlaod URL link. Drop 3.6, add 3.11 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index db43c728f..d78268723 100644 --- a/setup.py +++ b/setup.py @@ -240,18 +240,18 @@ def subdirs(root, file="*.*", depth=10): description="A general-purpose computer algebra system.", license="GPL", url="https://mathics.org/", - download_url="https://github.com/Mathics/mathics-core/releases", + download_url="https://github.com/Mathics3/mathics-core/releases", keywords=["Mathematica", "Wolfram", "Interpreter", "Shell", "Math", "CAS"], classifiers=[ "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Programming Language :: Python", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Scientific/Engineering", From b169eb9d650f206734cee68ce91cab5d503ce8d0 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 19 Nov 2023 12:25:14 -0500 Subject: [PATCH 20/51] More administrivia... PYPI no longer supports eggs. --- admin-tools/make-dist.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin-tools/make-dist.sh b/admin-tools/make-dist.sh index 015477da8..d2f045dd0 100755 --- a/admin-tools/make-dist.sh +++ b/admin-tools/make-dist.sh @@ -25,7 +25,8 @@ for pyversion in $PYVERSIONS; do exit $? fi rm -fr build - python setup.py bdist_egg + # PYPI no longer supports eggs + # python setup.py bdist_egg python setup.py bdist_wheel done From c4b6752bec143d0d8604e151d23a996f6ab3233e Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Tue, 21 Nov 2023 14:23:33 +0800 Subject: [PATCH 21/51] Update tensors.py according to Pylint(R1721:unnecessary-comprehension) --- mathics/builtin/tensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index f084cdd26..47f7b4b14 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -526,7 +526,7 @@ def eval(self, d, type, evaluation: Evaluation): if isinstance(d, Integer) and type == SymbolSparseArray: d = d.get_int_value() - perms = list(permutations([i for i in range(1, d + 1)])) + perms = list(permutations(list(range(1, d + 1)))) rules = [ Expression( SymbolRule, From 7d6ae92d310de7214957b02b5ee2fea905b3f035 Mon Sep 17 00:00:00 2001 From: Kevin Cao Date: Wed, 22 Nov 2023 22:13:47 -0500 Subject: [PATCH 22/51] Fix small typo --- mathics/builtin/tensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index f084cdd26..d3cccff10 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -455,7 +455,7 @@ class Transpose(Builtin): :WMA: https://reference.wolfram.com/language/ref/Transpose.html)
    -
    'Tranpose[$m$]' +
    'Transpose[$m$]'
    transposes rows and columns in the matrix $m$.
    From 983049d23b0d23ac6bc4676f20a20fd84572e37f Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:35:25 +0800 Subject: [PATCH 23/51] Update SYMBOLS_MANIFEST.txt --- SYMBOLS_MANIFEST.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index d3ecf4204..43c6e2162 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -574,6 +574,7 @@ System`LessEqual System`LetterCharacter System`LetterNumber System`LetterQ +System`LeviCivitaTensor System`Level System`LevelQ System`LightBlue From 6126f08e56724130a4e56e38dcbd28e434662389 Mon Sep 17 00:00:00 2001 From: Kevin Cao Date: Thu, 23 Nov 2023 16:42:10 -0500 Subject: [PATCH 24/51] Add conjugate transpose function --- mathics/builtin/tensors.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index d3cccff10..03e01d54d 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -496,6 +496,28 @@ def eval(self, m, evaluation: Evaluation): result[col_index].append(item) return ListExpression(*[ListExpression(*row) for row in result]) +class ConjugateTranspose(Builtin): + """ + + :Conjugate transpose: https://en.wikipedia.org/wiki/Conjugate_transpose ( + :WMA: https://reference.wolfram.com/language/ref/ConjugateTranspose.html) + +
    +
    'ConjugateTranspose[$m$]' +
    gives the conjugate transpose of $m$. +
    + + >> ConjugateTranspose[{{0, I}, {0, 0}}] + = {{0, 0}, {-I, 0}} + + >> ConjugateTranspose[{{1, 2 I, 3}, {3 + 4 I, 5, I}}] + = {{1, 3 - 4 I}, {-2 I, 5}, {3, -I}} + """ + + rules = { + "ConjugateTranspose[m_]": "Conjugate[Transpose[m]]" + } + summary_text = "give the conjugate transpose" class LeviCivitaTensor(Builtin): """ From e171e0c95e2de78eb562ea04b46f8dd24d02b58b Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Fri, 24 Nov 2023 18:39:29 +0800 Subject: [PATCH 25/51] Fix Outer for SparseArray --- mathics/builtin/tensors.py | 114 +++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index e60f690ec..181a319bb 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -18,11 +18,12 @@ of any rank can be handled. """ +import itertools from sympy.combinatorics import Permutation from sympy.utilities.iterables import permutations -from mathics.core.atoms import Integer, String +from mathics.core.atoms import Integer, Integer0, Integer1, String from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED from mathics.core.builtin import BinaryOperator, Builtin from mathics.core.convert.python import from_python @@ -30,7 +31,7 @@ from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolRule, SymbolSparseArray +from mathics.core.systemsymbols import SymbolAutomatic, SymbolRule, SymbolSparseArray from mathics.eval.parts import get_part @@ -299,21 +300,25 @@ class Outer(Builtin): = {{0, 1, 0}, {1, 0, 1}, {0, ComplexInfinity, 0}} """ + rules = { + "Outer[f_, a___, b_SparseArray, c___] /; UnsameQ[f, Times]": "Outer[f, a, b // Normal, c]", + } + summary_text = "generalized outer product" def eval(self, f, lists, evaluation: Evaluation): - "Outer[f_, lists__]" + "Outer[f_, lists__] /; Or[SameQ[f, Times], Not[MemberQ[{lists}, _SparseArray]]]" lists = lists.get_sequence() head = None - for list in lists: - if isinstance(list, Atom): + for _list in lists: + if isinstance(_list, Atom): evaluation.message("Outer", "normal") return if head is None: - head = list.head - elif not list.head.sameQ(head): - evaluation.message("Outer", "heads", head, list.head) + head = _list.head + elif not _list.head.sameQ(head): + evaluation.message("Outer", "heads", head, _list.head) return def rec(item, rest_lists, current): @@ -329,7 +334,73 @@ def rec(item, rest_lists, current): elements.append(rec(element, rest_lists, current)) return Expression(head, *elements) - return rec(lists[0], lists[1:], []) + def rec_sparse(item, rest_lists, current): + evaluation.check_stopped() + if isinstance(item, tuple): # (rules) + elements = [] + for element in item: + rec_temp = rec_sparse(element, rest_lists, current) + if isinstance(rec_temp, tuple): + elements.extend(rec_temp) + else: + elements.append(rec_temp) + return tuple(elements) + else: # rule + _pos, _val = item.elements + if rest_lists: + return rec_sparse( + rest_lists[0], + rest_lists[1:], + (current[0] + _pos.elements, current[1] * _val), + ) + else: + return Expression( + SymbolRule, + ListExpression(*(current[0] + _pos.elements)), + current[1] * _val, + ) + + if head.sameQ(SymbolSparseArray): + dims = [] + val = Integer1 + data = [] # data = [(rules), ...] + for _list in lists: + dims.extend(_list.elements[1]) + val *= _list.elements[2] + if _list.elements[2] == Integer0: # _val==0 + data.append(_list.elements[3].elements) # append (rules) + else: # _val!=0, append (rules, other pos->_val) + other_pos = [] + for pos in itertools.product( + *(range(1, d.value + 1) for d in _list.elements[1]) + ): + other_pos.append( + ListExpression(*(Integer(i) for i in pos)) + ) # generate all pos + rules_pos = set( + rule.elements[0] for rule in _list.elements[3].elements + ) # pos of existing rules + other_pos = ( + set(other_pos) - rules_pos + ) # remove pos of existing rules + other_rules = [] + for pos in other_pos: + other_rules.append( + Expression(SymbolRule, pos, _list.elements[2]) + ) # generate other pos->_val + data.append( + _list.elements[3].elements + tuple(other_rules) + ) # append (rules, other pos->_val) + dims = ListExpression(*dims) + return Expression( + SymbolSparseArray, + SymbolAutomatic, + dims, + val, + ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), + ) + else: + return rec(lists[0], lists[1:], []) class RotationTransform(Builtin): @@ -455,7 +526,7 @@ class Transpose(Builtin): :WMA: https://reference.wolfram.com/language/ref/Transpose.html)
    -
    'Transpose[$m$]' +
    'Tranpose[$m$]'
    transposes rows and columns in the matrix $m$.
    @@ -497,6 +568,27 @@ def eval(self, m, evaluation: Evaluation): return ListExpression(*[ListExpression(*row) for row in result]) +class TensorProduct(Builtin): + """ + :Tensor product:https://en.wikipedia.org/wiki/Tensor_product \ + (:WMA link:https://reference.wolfram.com/language/ref/TensorProduct.html) + +
    +
    'IdentityMatrix[$n$]' +
    gives the identity matrix with $n$ rows and columns. +
    + + >> IdentityMatrix[3] + = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} + """ + + rules = { + "IdentityMatrix[n_Integer]": "DiagonalMatrix[Table[1, {n}]]", + } + + summary_text = "give the identity matrix with a given dimension" + + class LeviCivitaTensor(Builtin): """ :Levi-Civita tensor:https://en.wikipedia.org/wiki/Levi-Civita_symbol \ @@ -526,7 +618,7 @@ def eval(self, d, type, evaluation: Evaluation): if isinstance(d, Integer) and type == SymbolSparseArray: d = d.get_int_value() - perms = list(permutations(list(range(1, d + 1)))) + perms = list(permutations([i for i in range(1, d + 1)])) rules = [ Expression( SymbolRule, From e7b68a3a050cc1f20e951e7297f36665fe030941 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Fri, 24 Nov 2023 18:43:01 +0800 Subject: [PATCH 26/51] Update tensors.py fix small typo --- mathics/builtin/tensors.py | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 181a319bb..a731908c0 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -526,7 +526,7 @@ class Transpose(Builtin): :WMA: https://reference.wolfram.com/language/ref/Transpose.html)
    -
    'Tranpose[$m$]' +
    'Transpose[$m$]'
    transposes rows and columns in the matrix $m$.
    @@ -568,27 +568,6 @@ def eval(self, m, evaluation: Evaluation): return ListExpression(*[ListExpression(*row) for row in result]) -class TensorProduct(Builtin): - """ - :Tensor product:https://en.wikipedia.org/wiki/Tensor_product \ - (:WMA link:https://reference.wolfram.com/language/ref/TensorProduct.html) - -
    -
    'IdentityMatrix[$n$]' -
    gives the identity matrix with $n$ rows and columns. -
    - - >> IdentityMatrix[3] - = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} - """ - - rules = { - "IdentityMatrix[n_Integer]": "DiagonalMatrix[Table[1, {n}]]", - } - - summary_text = "give the identity matrix with a given dimension" - - class LeviCivitaTensor(Builtin): """ :Levi-Civita tensor:https://en.wikipedia.org/wiki/Levi-Civita_symbol \ @@ -618,7 +597,7 @@ def eval(self, d, type, evaluation: Evaluation): if isinstance(d, Integer) and type == SymbolSparseArray: d = d.get_int_value() - perms = list(permutations([i for i in range(1, d + 1)])) + perms = list(permutations(list(range(1, d + 1)))) rules = [ Expression( SymbolRule, From 0789bd6c7ed94e998195bc12362c6810ceaf07e7 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Fri, 24 Nov 2023 21:27:45 +0800 Subject: [PATCH 27/51] Update tensors.py --- mathics/builtin/tensors.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index a731908c0..24cb62c87 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -339,11 +339,7 @@ def rec_sparse(item, rest_lists, current): if isinstance(item, tuple): # (rules) elements = [] for element in item: - rec_temp = rec_sparse(element, rest_lists, current) - if isinstance(rec_temp, tuple): - elements.extend(rec_temp) - else: - elements.append(rec_temp) + elements.extend(rec_sparse(element, rest_lists, current)) return tuple(elements) else: # rule _pos, _val = item.elements @@ -354,12 +350,13 @@ def rec_sparse(item, rest_lists, current): (current[0] + _pos.elements, current[1] * _val), ) else: - return Expression( - SymbolRule, - ListExpression(*(current[0] + _pos.elements)), - current[1] * _val, + return ( + Expression( + SymbolRule, + ListExpression(*(current[0] + _pos.elements)), + current[1] * _val, + ), ) - if head.sameQ(SymbolSparseArray): dims = [] val = Integer1 From 71b3f3b46e4daa569a4b01b4ac80e54a03fb2fe0 Mon Sep 17 00:00:00 2001 From: Kevin Cao Date: Fri, 24 Nov 2023 11:02:46 -0500 Subject: [PATCH 28/51] Pass black checks --- mathics/builtin/tensors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 03e01d54d..831a4de5a 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -496,6 +496,7 @@ def eval(self, m, evaluation: Evaluation): result[col_index].append(item) return ListExpression(*[ListExpression(*row) for row in result]) + class ConjugateTranspose(Builtin): """ @@ -515,10 +516,11 @@ class ConjugateTranspose(Builtin): """ rules = { - "ConjugateTranspose[m_]": "Conjugate[Transpose[m]]" + "ConjugateTranspose[m_]": "Conjugate[Transpose[m]]", } summary_text = "give the conjugate transpose" + class LeviCivitaTensor(Builtin): """ :Levi-Civita tensor:https://en.wikipedia.org/wiki/Levi-Civita_symbol \ From 0c74fb66019348a79fbf1521cf8bc44d81e70b03 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 25 Nov 2023 01:32:10 +0000 Subject: [PATCH 29/51] Add Kevin Cao --- AUTHORS.txt | 1 + CHANGES.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 206990c35..b9b464147 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -37,6 +37,7 @@ Additional contributions were made by: - Rocky Bernstein @rocky - Tiago Cavalcante Trindade @TiagoCavalcante - Li Xiang @Li-Xiang-Ideal +- Kevin Cao Zou @kejcao Thanks to the authors of all projects that are used in Mathics: - Django diff --git a/CHANGES.rst b/CHANGES.rst index c9b22ebaf..2729acab9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,7 @@ New Builtins * ``Elements`` +* ``ConjugateTranspose`` * ``LeviCivitaTensor`` * ``RealAbs`` and ``RealSign`` * ``RealValuedNumberQ`` From 9e2b2c83d1026a96932d6b092947c953e08ec821 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 25 Nov 2023 01:32:48 +0000 Subject: [PATCH 30/51] Correct name --- AUTHORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index b9b464147..03cdcf44b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -37,7 +37,7 @@ Additional contributions were made by: - Rocky Bernstein @rocky - Tiago Cavalcante Trindade @TiagoCavalcante - Li Xiang @Li-Xiang-Ideal -- Kevin Cao Zou @kejcao +- Kevin Cao @kejcao Thanks to the authors of all projects that are used in Mathics: - Django From 58dbb8b6aabc197857d1c17d6aab4cbf6ad9f984 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:13:48 +0800 Subject: [PATCH 31/51] Update tensors.py --- mathics/builtin/tensors.py | 72 +++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index f8a292524..3509920c4 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -357,48 +357,40 @@ def rec_sparse(item, rest_lists, current): current[1] * _val, ), ) - if head.sameQ(SymbolSparseArray): - dims = [] - val = Integer1 - data = [] # data = [(rules), ...] - for _list in lists: - dims.extend(_list.elements[1]) - val *= _list.elements[2] - if _list.elements[2] == Integer0: # _val==0 - data.append(_list.elements[3].elements) # append (rules) - else: # _val!=0, append (rules, other pos->_val) - other_pos = [] - for pos in itertools.product( - *(range(1, d.value + 1) for d in _list.elements[1]) - ): - other_pos.append( - ListExpression(*(Integer(i) for i in pos)) - ) # generate all pos - rules_pos = set( - rule.elements[0] for rule in _list.elements[3].elements - ) # pos of existing rules - other_pos = ( - set(other_pos) - rules_pos - ) # remove pos of existing rules - other_rules = [] - for pos in other_pos: - other_rules.append( - Expression(SymbolRule, pos, _list.elements[2]) - ) # generate other pos->_val - data.append( - _list.elements[3].elements + tuple(other_rules) - ) # append (rules, other pos->_val) - dims = ListExpression(*dims) - return Expression( - SymbolSparseArray, - SymbolAutomatic, - dims, - val, - ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), - ) - else: + + # head != SparseArray + if not head.sameQ(SymbolSparseArray): return rec(lists[0], lists[1:], []) + # head == SparseArray + dims = [] + val = Integer1 + data = [] # data = [(rules), ...] + for _list in lists: + _dims, _val, _rules = _list.elements[1:] + dims.extend(_dims) + val *= _val + if _val == Integer0: # _val==0, append (_rules) + data.append(_rules.elements) + else: # _val!=0, append (_rules, other pos->_val) + other_pos = [] + for pos in itertools.product(*(range(1, d.value + 1) for d in _dims)): + other_pos.append(ListExpression(*(Integer(i) for i in pos))) + rules_pos = set(rule.elements[0] for rule in _rules.elements) + other_pos = set(other_pos) - rules_pos + other_rules = [] + for pos in other_pos: + other_rules.append(Expression(SymbolRule, pos, _val)) + data.append(_list.elements[3].elements + tuple(other_rules)) + dims = ListExpression(*dims) + return Expression( + SymbolSparseArray, + SymbolAutomatic, + dims, + val, + ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), + ) + class RotationTransform(Builtin): """ From 9583f389506cddf275035ebe874d1d32d0c6a74d Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:48:15 +0800 Subject: [PATCH 32/51] Update tensors.py --- mathics/builtin/tensors.py | 43 +++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 3509920c4..2b9b9d414 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -30,8 +30,20 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolAutomatic, SymbolRule, SymbolSparseArray +from mathics.core.symbols import ( + Atom, + Symbol, + SymbolFalse, + SymbolList, + SymbolTimes, + SymbolTrue, +) +from mathics.core.systemsymbols import ( + SymbolAutomatic, + SymbolNormal, + SymbolRule, + SymbolSparseArray, +) from mathics.eval.parts import get_part @@ -300,26 +312,42 @@ class Outer(Builtin): = {{0, 1, 0}, {1, 0, 1}, {0, ComplexInfinity, 0}} """ - rules = { - "Outer[f_, a___, b_SparseArray, c___] /; UnsameQ[f, Times]": "Outer[f, a, b // Normal, c]", - } - summary_text = "generalized outer product" def eval(self, f, lists, evaluation: Evaluation): - "Outer[f_, lists__] /; Or[SameQ[f, Times], Not[MemberQ[{lists}, _SparseArray]]]" + "Outer[f_, lists__]" + # If f=!=Times, or lists contain both SparseArray and List, then convert all SparseArrays to Lists lists = lists.get_sequence() head = None + sparse_to_list = f != SymbolTimes + contain_sparse = False + comtain_list = False + for _list in lists: + if _list.head.sameQ(SymbolSparseArray): + contain_sparse = True + if _list.head.sameQ(SymbolList): + comtain_list = True + sparse_to_list = sparse_to_list or (contain_sparse and comtain_list) + if sparse_to_list: + break + if sparse_to_list: + new_lists = [] for _list in lists: if isinstance(_list, Atom): evaluation.message("Outer", "normal") return + if sparse_to_list: + if _list.head.sameQ(SymbolSparseArray): + _list = Expression(SymbolNormal, _list).evaluate(evaluation) + new_lists.append(_list) if head is None: head = _list.head elif not _list.head.sameQ(head): evaluation.message("Outer", "heads", head, _list.head) return + if sparse_to_list: + lists = new_lists def rec(item, rest_lists, current): evaluation.check_stopped() @@ -391,7 +419,6 @@ def rec_sparse(item, rest_lists, current): ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), ) - class RotationTransform(Builtin): """ :WMA link: https://reference.wolfram.com/language/ref/RotationTransform.html From f79957688056ce38dc650cc3fdee104c144c2d43 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:03:15 +0800 Subject: [PATCH 33/51] Update isort-and-black-checks.yml --- .github/workflows/isort-and-black-checks.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/isort-and-black-checks.yml b/.github/workflows/isort-and-black-checks.yml index 37cde2a21..64cf364b9 100644 --- a/.github/workflows/isort-and-black-checks.yml +++ b/.github/workflows/isort-and-black-checks.yml @@ -4,7 +4,11 @@ # https://github.com/cclauss/autoblack name: isort and black check -on: [pull_request] +on: + push: + branches: [ master ] + pull_request: + branches: '**' jobs: build: runs-on: ubuntu-latest From f09b340b5fd13ec186802174d349141f0a05921c Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:09:35 +0800 Subject: [PATCH 34/51] Update tensors.py --- mathics/builtin/tensors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 2b9b9d414..194bc8d19 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -419,6 +419,7 @@ def rec_sparse(item, rest_lists, current): ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), ) + class RotationTransform(Builtin): """ :WMA link: https://reference.wolfram.com/language/ref/RotationTransform.html From 6dddce8f930c705d670f08056b28170d126af313 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:21:38 +0800 Subject: [PATCH 35/51] Update isort-and-black-checks.yml --- .github/workflows/isort-and-black-checks.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/isort-and-black-checks.yml b/.github/workflows/isort-and-black-checks.yml index 64cf364b9..37cde2a21 100644 --- a/.github/workflows/isort-and-black-checks.yml +++ b/.github/workflows/isort-and-black-checks.yml @@ -4,11 +4,7 @@ # https://github.com/cclauss/autoblack name: isort and black check -on: - push: - branches: [ master ] - pull_request: - branches: '**' +on: [pull_request] jobs: build: runs-on: ubuntu-latest From 12917a151c774c3db969589a61a20a26fdec13df Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:45:12 +0800 Subject: [PATCH 36/51] Update osx.yml --- .github/workflows/osx.yml | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 2269693ac..159024143 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -2,36 +2,29 @@ name: Mathics3 (OSX) on: push: - branches: [ master ] + branches: [master] pull_request: branches: '**' jobs: build: - env: - LDFLAGS: "-L/usr/local/opt/llvm@11/lib" - CPPFLAGS: "-I/usr/local/opt/llvm@11/include" runs-on: macos-latest strategy: matrix: os: [macOS] python-version: ['3.9', '3.10'] steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install OS dependencies - run: | - brew install llvm tesseract - python -m pip install --upgrade pip - - name: Install Mathics3 with full Python dependencies - run: | - # We can comment out after next Mathics-Scanner release - # python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full] - python -m pip install Mathics-Scanner - make develop-full - - name: Test Mathics3 - run: | - make -j3 check + - uses: actions/checkout@v3 + - name: Install OS dependencies + run: | + brew install llvm@11 tesseract + python -m pip install --upgrade pip + - name: Install Mathics-Scanner + run: | + python -m pip install Mathics-Scanner + - name: Install Mathics3 with full Python dependencies + run: | + make develop-full + - name: Test Mathics3 + run: | + make -j3 check From bbfc74cc60be4bdee411d727dd245edbd3ece57d Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:52:51 +0800 Subject: [PATCH 37/51] Update osx.yml --- .github/workflows/osx.yml | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 159024143..308bef106 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -2,29 +2,36 @@ name: Mathics3 (OSX) on: push: - branches: [master] + branches: [ master ] pull_request: branches: '**' jobs: build: + env: + LDFLAGS: "-L/usr/local/opt/llvm@11/lib" + CPPFLAGS: "-I/usr/local/opt/llvm@11/include" runs-on: macos-latest strategy: matrix: os: [macOS] python-version: ['3.9', '3.10'] steps: - - uses: actions/checkout@v3 - - name: Install OS dependencies - run: | - brew install llvm@11 tesseract - python -m pip install --upgrade pip - - name: Install Mathics-Scanner - run: | - python -m pip install Mathics-Scanner - - name: Install Mathics3 with full Python dependencies - run: | - make develop-full - - name: Test Mathics3 - run: | - make -j3 check + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install OS dependencies + run: | + brew install llvm@11 tesseract + python -m pip install --upgrade pip + - name: Install Mathics3 with full Python dependencies + run: | + # We can comment out after next Mathics-Scanner release + # python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full] + python -m pip install Mathics-Scanner + make develop-full + - name: Test Mathics3 + run: | + make -j3 check From 9be0ca7d6d1080fdb57a67434f5b10bb9a3072c2 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:04:37 +0800 Subject: [PATCH 38/51] Update osx.yml --- .github/workflows/osx.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 308bef106..ed800bae4 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -9,8 +9,8 @@ on: jobs: build: env: - LDFLAGS: "-L/usr/local/opt/llvm@11/lib" - CPPFLAGS: "-I/usr/local/opt/llvm@11/include" + LDFLAGS: "-L/usr/local/opt/llvm/lib" + CPPFLAGS: "-I/usr/local/opt/llvm/include" runs-on: macos-latest strategy: matrix: @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install OS dependencies run: | - brew install llvm@11 tesseract + brew install llvm tesseract python -m pip install --upgrade pip - name: Install Mathics3 with full Python dependencies run: | From bb133b1ccf15a47931db0a3b5607561a64d09a36 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:10:05 +0800 Subject: [PATCH 39/51] Update osx.yml --- .github/workflows/osx.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index ed800bae4..4f0eb189c 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -9,8 +9,8 @@ on: jobs: build: env: - LDFLAGS: "-L/usr/local/opt/llvm/lib" - CPPFLAGS: "-I/usr/local/opt/llvm/include" + LDFLAGS: "-L/usr/local/opt/llvm@17/lib" + CPPFLAGS: "-I/usr/local/opt/llvm@17/include" runs-on: macos-latest strategy: matrix: @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install OS dependencies run: | - brew install llvm tesseract + brew install llvm@17 tesseract python -m pip install --upgrade pip - name: Install Mathics3 with full Python dependencies run: | From 8a0057f2bbb9865668809324a5921d288b5e2df6 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:54:26 +0800 Subject: [PATCH 40/51] Update osx.yml Not sure what the latest llvm that supports Python 3.9 is. Try one by one :( --- .github/workflows/osx.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 4f0eb189c..da640f1b2 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -9,8 +9,8 @@ on: jobs: build: env: - LDFLAGS: "-L/usr/local/opt/llvm@17/lib" - CPPFLAGS: "-I/usr/local/opt/llvm@17/include" + LDFLAGS: "-L/usr/local/opt/llvm@14/lib" + CPPFLAGS: "-I/usr/local/opt/llvm@14/include" runs-on: macos-latest strategy: matrix: @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install OS dependencies run: | - brew install llvm@17 tesseract + brew install llvm@14 tesseract python -m pip install --upgrade pip - name: Install Mathics3 with full Python dependencies run: | From 353b868c540d92b32605e8f661f4cd1226269939 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 11:52:24 +0800 Subject: [PATCH 41/51] Update osx.yml --- .github/workflows/osx.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 2269693ac..da640f1b2 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -9,8 +9,8 @@ on: jobs: build: env: - LDFLAGS: "-L/usr/local/opt/llvm@11/lib" - CPPFLAGS: "-I/usr/local/opt/llvm@11/include" + LDFLAGS: "-L/usr/local/opt/llvm@14/lib" + CPPFLAGS: "-I/usr/local/opt/llvm@14/include" runs-on: macos-latest strategy: matrix: @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install OS dependencies run: | - brew install llvm tesseract + brew install llvm@14 tesseract python -m pip install --upgrade pip - name: Install Mathics3 with full Python dependencies run: | From e235c04f94b6f59d5cd75d30a7f0a90f6a14da62 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Sun, 26 Nov 2023 12:38:25 +0800 Subject: [PATCH 42/51] Update tensors.py Add some tests --- mathics/builtin/tensors.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 194bc8d19..d658728fe 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -291,10 +291,21 @@ class Outer(Builtin): Outer product of two matrices: >> Outer[Times, {{a, b}, {c, d}}, {{1, 2}, {3, 4}}] = {{{{a, 2 a}, {3 a, 4 a}}, {{b, 2 b}, {3 b, 4 b}}}, {{{c, 2 c}, {3 c, 4 c}}, {{d, 2 d}, {3 d, 4 d}}}} + + Outer product of two sparse arrays: + >> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]] + = SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} -> a c, {1, 2, 2, 1} -> a d, {2, 1, 1, 2} -> b c, {2, 1, 2, 1} -> b d}] 'Outer' of multiple lists: >> Outer[f, {a, b}, {x, y, z}, {1, 2}] = {{{f[a, x, 1], f[a, x, 2]}, {f[a, y, 1], f[a, y, 2]}, {f[a, z, 1], f[a, z, 2]}}, {{f[b, x, 1], f[b, x, 2]}, {f[b, y, 1], f[b, y, 2]}, {f[b, z, 1], f[b, z, 2]}}} + + 'Outer' treats input sparse arrays as lists if f=!=Times, or if the input is a mixture of sparse arrays and lists: + >> Outer[f, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]] + = {{{{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}, {{f[a, 0], f[a, c]}, {f[a, d], f[a, 0]}}}, {{{f[b, 0], f[b, c]}, {f[b, d], f[b, 0]}}, {{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}}} + + >> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], {c, d}] + = {{{0, 0}, {a c, a d}}, {{b c, b d}, {0, 0}}} Arrays can be ragged: >> Outer[Times, {{1, 2}}, {{a, b}, {c, d, e}}] From 4db5ea0f9783a0e48056d4ed79d6c1fddeb4d33a Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:05:38 +0800 Subject: [PATCH 43/51] fix typo --- mathics/builtin/tensors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index d658728fe..8a6921e65 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -333,13 +333,13 @@ def eval(self, f, lists, evaluation: Evaluation): head = None sparse_to_list = f != SymbolTimes contain_sparse = False - comtain_list = False + contain_list = False for _list in lists: if _list.head.sameQ(SymbolSparseArray): contain_sparse = True if _list.head.sameQ(SymbolList): - comtain_list = True - sparse_to_list = sparse_to_list or (contain_sparse and comtain_list) + contain_list = True + sparse_to_list = sparse_to_list or (contain_sparse and contain_list) if sparse_to_list: break if sparse_to_list: From 792d218529e8ba2e11af957b68382759ce30d853 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:17:12 +0800 Subject: [PATCH 44/51] move eval from ``mathics.builtin.tensors`` to here --- mathics/eval/tensors.py | 244 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 mathics/eval/tensors.py diff --git a/mathics/eval/tensors.py b/mathics/eval/tensors.py new file mode 100644 index 000000000..596c792b0 --- /dev/null +++ b/mathics/eval/tensors.py @@ -0,0 +1,244 @@ +import itertools + +from sympy.combinatorics import Permutation +from sympy.utilities.iterables import permutations + +from mathics.core.atoms import Integer, Integer0, Integer1, String +from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import ( + Atom, + Symbol, + SymbolFalse, + SymbolList, + SymbolTimes, + SymbolTrue, +) +from mathics.core.systemsymbols import ( + SymbolAutomatic, + SymbolNormal, + SymbolRule, + SymbolSparseArray, +) +from mathics.eval.parts import get_part + + +def get_default_distance(p): + if all(q.is_numeric() for q in p): + return Symbol("SquaredEuclideanDistance") + elif all(q.get_head_name() == "System`List" for q in p): + dimensions = [get_dimensions(q) for q in p] + if len(dimensions) < 1: + return None + d0 = dimensions[0] + if not all(d == d0 for d in dimensions[1:]): + return None + if len(dimensions[0]) == 1: # vectors? + + def is_boolean(x): + return x.get_head_name() == "System`Symbol" and x in ( + SymbolTrue, + SymbolFalse, + ) + + if all(all(is_boolean(e) for e in q.elements) for q in p): + return Symbol("JaccardDissimilarity") + return Symbol("SquaredEuclideanDistance") + elif all(isinstance(q, String) for q in p): + return Symbol("EditDistance") + else: + from mathics.builtin.colors.color_directives import expression_to_color + + if all(expression_to_color(q) is not None for q in p): + return Symbol("ColorDistance") + + return None + + +def get_dimensions(expr, head=None): + if isinstance(expr, Atom): + return [] + else: + if head is not None and not expr.head.sameQ(head): + return [] + sub_dim = None + sub = [] + for element in expr.elements: + sub = get_dimensions(element, expr.head) + if sub_dim is None: + sub_dim = sub + else: + if sub_dim != sub: + sub = [] + break + return [len(expr.elements)] + sub + + +def eval_Inner(f, list1, list2, g, evaluation: Evaluation): + "Evaluates recursively the inner product of list1 and list2" + + m = get_dimensions(list1) + n = get_dimensions(list2) + + if not m or not n: + evaluation.message("Inner", "normal") + return + if list1.get_head() != list2.get_head(): + evaluation.message("Inner", "heads", list1.get_head(), list2.get_head()) + return + if m[-1] != n[0]: + evaluation.message("Inner", "incom", m[-1], len(m), list1, n[0], list2) + return + + head = list1.get_head() + inner_dim = n[0] + + def rec(i_cur, j_cur, i_rest, j_rest): + evaluation.check_stopped() + if i_rest: + elements = [] + for i in range(1, i_rest[0] + 1): + elements.append(rec(i_cur + [i], j_cur, i_rest[1:], j_rest)) + return Expression(head, *elements) + elif j_rest: + elements = [] + for j in range(1, j_rest[0] + 1): + elements.append(rec(i_cur, j_cur + [j], i_rest, j_rest[1:])) + return Expression(head, *elements) + else: + + def summand(i): + part1 = get_part(list1, i_cur + [i]) + part2 = get_part(list2, [i] + j_cur) + return Expression(f, part1, part2) + + part = Expression(g, *[summand(i) for i in range(1, inner_dim + 1)]) + # cur_expr.elements.append(part) + return part + + return rec([], [], m[:-1], n[1:]) + + +def eval_Outer(f, lists, evaluation: Evaluation): + "Evaluates recursively the outer product of lists" + + # If f=!=Times, or lists contain both SparseArray and List, then convert all SparseArrays to Lists + lists = lists.get_sequence() + head = None + sparse_to_list = f != SymbolTimes + contain_sparse = False + contain_list = False + for _list in lists: + if _list.head.sameQ(SymbolSparseArray): + contain_sparse = True + if _list.head.sameQ(SymbolList): + contain_list = True + sparse_to_list = sparse_to_list or (contain_sparse and contain_list) + if sparse_to_list: + break + if sparse_to_list: + new_lists = [] + for _list in lists: + if isinstance(_list, Atom): + evaluation.message("Outer", "normal") + return + if sparse_to_list: + if _list.head.sameQ(SymbolSparseArray): + _list = Expression(SymbolNormal, _list).evaluate(evaluation) + new_lists.append(_list) + if head is None: + head = _list.head + elif not _list.head.sameQ(head): + evaluation.message("Outer", "heads", head, _list.head) + return + if sparse_to_list: + lists = new_lists + + def rec(item, rest_lists, current): + evaluation.check_stopped() + if isinstance(item, Atom) or not item.head.sameQ(head): + if rest_lists: + return rec(rest_lists[0], rest_lists[1:], current + [item]) + else: + return Expression(f, *(current + [item])) + else: + elements = [] + for element in item.elements: + elements.append(rec(element, rest_lists, current)) + return Expression(head, *elements) + + def rec_sparse(item, rest_lists, current): + evaluation.check_stopped() + if isinstance(item, tuple): # (rules) + elements = [] + for element in item: + elements.extend(rec_sparse(element, rest_lists, current)) + return tuple(elements) + else: # rule + _pos, _val = item.elements + if rest_lists: + return rec_sparse( + rest_lists[0], + rest_lists[1:], + (current[0] + _pos.elements, current[1] * _val), + ) + else: + return ( + Expression( + SymbolRule, + ListExpression(*(current[0] + _pos.elements)), + current[1] * _val, + ), + ) + + # head != SparseArray + if not head.sameQ(SymbolSparseArray): + return rec(lists[0], lists[1:], []) + + # head == SparseArray + dims = [] + val = Integer1 + data = [] # data = [(rules), ...] + for _list in lists: + _dims, _val, _rules = _list.elements[1:] + dims.extend(_dims) + val *= _val + if _val == Integer0: # _val==0, append (_rules) + data.append(_rules.elements) + else: # _val!=0, append (_rules, other pos->_val) + other_pos = [] + for pos in itertools.product(*(range(1, d.value + 1) for d in _dims)): + other_pos.append(ListExpression(*(Integer(i) for i in pos))) + rules_pos = set(rule.elements[0] for rule in _rules.elements) + other_pos = set(other_pos) - rules_pos + other_rules = [] + for pos in other_pos: + other_rules.append(Expression(SymbolRule, pos, _val)) + data.append(_rules.elements + tuple(other_rules)) + dims = ListExpression(*dims) + return Expression( + SymbolSparseArray, + SymbolAutomatic, + dims, + val, + ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), + ) + + +def eval_LeviCivitaTensor(d, type): + "Evaluates Levi-Civita tensor of rank d" + + if isinstance(d, Integer) and type == SymbolSparseArray: + d = d.get_int_value() + perms = list(permutations(list(range(1, d + 1)))) + rules = [ + Expression( + SymbolRule, + from_python(p), + from_python(Permutation.from_sequence(p).signature()), + ) + for p in perms + ] + return Expression(SymbolSparseArray, from_python(rules), from_python([d] * d)) From 312eaff7595e41be8f7f97ad4926f73da57068f4 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:19:42 +0800 Subject: [PATCH 45/51] Update clusters.py --- mathics/builtin/distance/clusters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/distance/clusters.py b/mathics/builtin/distance/clusters.py index ebd904540..afd0b36f8 100644 --- a/mathics/builtin/distance/clusters.py +++ b/mathics/builtin/distance/clusters.py @@ -139,7 +139,7 @@ def _cluster(self, p, k, mode, evaluation, options, expr): options, "DistanceFunction", evaluation ) if distance_function_string == "Automatic": - from mathics.builtin.tensors import get_default_distance + from mathics.eval.tensors import get_default_distance distance_function = get_default_distance(dist_p) if distance_function is None: @@ -462,7 +462,7 @@ def eval( options, "DistanceFunction", evaluation ) if distance_function_string == "Automatic": - from mathics.builtin.tensors import get_default_distance + from mathics.eval.tensors import get_default_distance distance_function = get_default_distance(dist_p) if distance_function is None: From 088cbed9c3ee72ad14b34beb0b3e49ed8c947903 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:28:56 +0800 Subject: [PATCH 46/51] Update tensors.py move almost all ``eval()`` to ``mathics.eval.tensors`` --- mathics/builtin/tensors.py | 238 ++----------------------------------- 1 file changed, 10 insertions(+), 228 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 8a6921e65..bad1a83f4 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -18,84 +18,18 @@ of any rank can be handled. """ -import itertools -from sympy.combinatorics import Permutation -from sympy.utilities.iterables import permutations - -from mathics.core.atoms import Integer, Integer0, Integer1, String +from mathics.core.atoms import Integer from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED from mathics.core.builtin import BinaryOperator, Builtin -from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation -from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolFalse, - SymbolList, - SymbolTimes, - SymbolTrue, -) -from mathics.core.systemsymbols import ( - SymbolAutomatic, - SymbolNormal, - SymbolRule, - SymbolSparseArray, +from mathics.eval.tensors import ( + eval_Inner, + eval_LeviCivitaTensor, + eval_Outer, + get_dimensions, ) -from mathics.eval.parts import get_part - - -def get_default_distance(p): - if all(q.is_numeric() for q in p): - return Symbol("SquaredEuclideanDistance") - elif all(q.get_head_name() == "System`List" for q in p): - dimensions = [get_dimensions(q) for q in p] - if len(dimensions) < 1: - return None - d0 = dimensions[0] - if not all(d == d0 for d in dimensions[1:]): - return None - if len(dimensions[0]) == 1: # vectors? - - def is_boolean(x): - return x.get_head_name() == "System`Symbol" and x in ( - SymbolTrue, - SymbolFalse, - ) - - if all(all(is_boolean(e) for e in q.elements) for q in p): - return Symbol("JaccardDissimilarity") - return Symbol("SquaredEuclideanDistance") - elif all(isinstance(q, String) for q in p): - return Symbol("EditDistance") - else: - from mathics.builtin.colors.color_directives import expression_to_color - - if all(expression_to_color(q) is not None for q in p): - return Symbol("ColorDistance") - - return None - - -def get_dimensions(expr, head=None): - if isinstance(expr, Atom): - return [] - else: - if head is not None and not expr.head.sameQ(head): - return [] - sub_dim = None - sub = [] - for element in expr.elements: - sub = get_dimensions(element, expr.head) - if sub_dim is None: - sub_dim = sub - else: - if sub_dim != sub: - sub = [] - break - return [len(expr.elements)] + sub class ArrayDepth(Builtin): @@ -233,46 +167,7 @@ class Inner(Builtin): def eval(self, f, list1, list2, g, evaluation: Evaluation): "Inner[f_, list1_, list2_, g_]" - m = get_dimensions(list1) - n = get_dimensions(list2) - - if not m or not n: - evaluation.message("Inner", "normal") - return - if list1.get_head() != list2.get_head(): - evaluation.message("Inner", "heads", list1.get_head(), list2.get_head()) - return - if m[-1] != n[0]: - evaluation.message("Inner", "incom", m[-1], len(m), list1, n[0], list2) - return - - head = list1.get_head() - inner_dim = n[0] - - def rec(i_cur, j_cur, i_rest, j_rest): - evaluation.check_stopped() - if i_rest: - elements = [] - for i in range(1, i_rest[0] + 1): - elements.append(rec(i_cur + [i], j_cur, i_rest[1:], j_rest)) - return Expression(head, *elements) - elif j_rest: - elements = [] - for j in range(1, j_rest[0] + 1): - elements.append(rec(i_cur, j_cur + [j], i_rest, j_rest[1:])) - return Expression(head, *elements) - else: - - def summand(i): - part1 = get_part(list1, i_cur + [i]) - part2 = get_part(list2, [i] + j_cur) - return Expression(f, part1, part2) - - part = Expression(g, *[summand(i) for i in range(1, inner_dim + 1)]) - # cur_expr.elements.append(part) - return part - - return rec([], [], m[:-1], n[1:]) + return eval_Inner(f, list1, list2, g, evaluation) class Outer(Builtin): @@ -300,7 +195,7 @@ class Outer(Builtin): >> Outer[f, {a, b}, {x, y, z}, {1, 2}] = {{{f[a, x, 1], f[a, x, 2]}, {f[a, y, 1], f[a, y, 2]}, {f[a, z, 1], f[a, z, 2]}}, {{f[b, x, 1], f[b, x, 2]}, {f[b, y, 1], f[b, y, 2]}, {f[b, z, 1], f[b, z, 2]}}} - 'Outer' treats input sparse arrays as lists if f=!=Times, or if the input is a mixture of sparse arrays and lists: + 'Outer' converts input sparse arrays to lists if f=!=Times, or if the input is a mixture of sparse arrays and lists: >> Outer[f, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]] = {{{{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}, {{f[a, 0], f[a, c]}, {f[a, d], f[a, 0]}}}, {{{f[b, 0], f[b, c]}, {f[b, d], f[b, 0]}}, {{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}}} @@ -328,107 +223,7 @@ class Outer(Builtin): def eval(self, f, lists, evaluation: Evaluation): "Outer[f_, lists__]" - # If f=!=Times, or lists contain both SparseArray and List, then convert all SparseArrays to Lists - lists = lists.get_sequence() - head = None - sparse_to_list = f != SymbolTimes - contain_sparse = False - contain_list = False - for _list in lists: - if _list.head.sameQ(SymbolSparseArray): - contain_sparse = True - if _list.head.sameQ(SymbolList): - contain_list = True - sparse_to_list = sparse_to_list or (contain_sparse and contain_list) - if sparse_to_list: - break - if sparse_to_list: - new_lists = [] - for _list in lists: - if isinstance(_list, Atom): - evaluation.message("Outer", "normal") - return - if sparse_to_list: - if _list.head.sameQ(SymbolSparseArray): - _list = Expression(SymbolNormal, _list).evaluate(evaluation) - new_lists.append(_list) - if head is None: - head = _list.head - elif not _list.head.sameQ(head): - evaluation.message("Outer", "heads", head, _list.head) - return - if sparse_to_list: - lists = new_lists - - def rec(item, rest_lists, current): - evaluation.check_stopped() - if isinstance(item, Atom) or not item.head.sameQ(head): - if rest_lists: - return rec(rest_lists[0], rest_lists[1:], current + [item]) - else: - return Expression(f, *(current + [item])) - else: - elements = [] - for element in item.elements: - elements.append(rec(element, rest_lists, current)) - return Expression(head, *elements) - - def rec_sparse(item, rest_lists, current): - evaluation.check_stopped() - if isinstance(item, tuple): # (rules) - elements = [] - for element in item: - elements.extend(rec_sparse(element, rest_lists, current)) - return tuple(elements) - else: # rule - _pos, _val = item.elements - if rest_lists: - return rec_sparse( - rest_lists[0], - rest_lists[1:], - (current[0] + _pos.elements, current[1] * _val), - ) - else: - return ( - Expression( - SymbolRule, - ListExpression(*(current[0] + _pos.elements)), - current[1] * _val, - ), - ) - - # head != SparseArray - if not head.sameQ(SymbolSparseArray): - return rec(lists[0], lists[1:], []) - - # head == SparseArray - dims = [] - val = Integer1 - data = [] # data = [(rules), ...] - for _list in lists: - _dims, _val, _rules = _list.elements[1:] - dims.extend(_dims) - val *= _val - if _val == Integer0: # _val==0, append (_rules) - data.append(_rules.elements) - else: # _val!=0, append (_rules, other pos->_val) - other_pos = [] - for pos in itertools.product(*(range(1, d.value + 1) for d in _dims)): - other_pos.append(ListExpression(*(Integer(i) for i in pos))) - rules_pos = set(rule.elements[0] for rule in _rules.elements) - other_pos = set(other_pos) - rules_pos - other_rules = [] - for pos in other_pos: - other_rules.append(Expression(SymbolRule, pos, _val)) - data.append(_list.elements[3].elements + tuple(other_rules)) - dims = ListExpression(*dims) - return Expression( - SymbolSparseArray, - SymbolAutomatic, - dims, - val, - ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))), - ) + return eval_Outer(f, lists, evaluation) class RotationTransform(Builtin): @@ -647,17 +442,4 @@ class LeviCivitaTensor(Builtin): def eval(self, d, type, evaluation: Evaluation): "LeviCivitaTensor[d_Integer, type_]" - if isinstance(d, Integer) and type == SymbolSparseArray: - d = d.get_int_value() - perms = list(permutations(list(range(1, d + 1)))) - rules = [ - Expression( - SymbolRule, - from_python(p), - from_python(Permutation.from_sequence(p).signature()), - ) - for p in perms - ] - return Expression( - SymbolSparseArray, from_python(rules), from_python([d] * d) - ) + return eval_LeviCivitaTensor(d, type) From daf7d6dd8f6e69d9e0a20f759f9e97cd7a167730 Mon Sep 17 00:00:00 2001 From: Li Xiang <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:55:56 +0800 Subject: [PATCH 47/51] Update clusters.py --- mathics/builtin/distance/clusters.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mathics/builtin/distance/clusters.py b/mathics/builtin/distance/clusters.py index afd0b36f8..382fce982 100644 --- a/mathics/builtin/distance/clusters.py +++ b/mathics/builtin/distance/clusters.py @@ -35,6 +35,7 @@ ) from mathics.eval.nevaluator import eval_N from mathics.eval.parts import walk_levels +from mathics.eval.tensors import get_default_distance class _LazyDistances(LazyDistances): @@ -139,8 +140,6 @@ def _cluster(self, p, k, mode, evaluation, options, expr): options, "DistanceFunction", evaluation ) if distance_function_string == "Automatic": - from mathics.eval.tensors import get_default_distance - distance_function = get_default_distance(dist_p) if distance_function is None: name_of_builtin = strip_context(self.get_name()) @@ -462,8 +461,6 @@ def eval( options, "DistanceFunction", evaluation ) if distance_function_string == "Automatic": - from mathics.eval.tensors import get_default_distance - distance_function = get_default_distance(dist_p) if distance_function is None: evaluation.message( From 54a76477ad8d2a3755897d84b254048a6cd24718 Mon Sep 17 00:00:00 2001 From: Li-Xiang-Ideal <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Tue, 12 Dec 2023 16:44:11 +0800 Subject: [PATCH 48/51] Fix ArrayQ for SparseArray --- .../testing_expressions/list_oriented.py | 45 ++++-------- mathics/eval/testing_expressions.py | 69 ++++++++++++++++++- 2 files changed, 79 insertions(+), 35 deletions(-) diff --git a/mathics/builtin/testing_expressions/list_oriented.py b/mathics/builtin/testing_expressions/list_oriented.py index b99fe2040..cd643d618 100644 --- a/mathics/builtin/testing_expressions/list_oriented.py +++ b/mathics/builtin/testing_expressions/list_oriented.py @@ -7,10 +7,10 @@ from mathics.core.evaluation import Evaluation from mathics.core.exceptions import InvalidLevelspecError from mathics.core.expression import Expression -from mathics.core.rules import Pattern from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolSubsetQ +from mathics.core.systemsymbols import SymbolSparseArray, SymbolSubsetQ from mathics.eval.parts import python_levelspec +from mathics.eval.testing_expressions import check_ArrayQ, check_SparseArrayQ class ArrayQ(Builtin): @@ -39,6 +39,14 @@ class ArrayQ(Builtin): = False >> ArrayQ[{{a, b}, {c, d}}, 2, SymbolQ] = True + >> ArrayQ[SparseArray[{{1, 2} -> a, {2, 1} -> b}]] + = True + >> ArrayQ[SparseArray[{{1, 2} -> a, {2, 1} -> b}], 1] + = False + >> ArrayQ[SparseArray[{{1, 2} -> a, {2, 1} -> b}], 2, SymbolQ] + = False + >> ArrayQ[SparseArray[{{1, 1} -> a, {1, 2} -> b}], 2, SymbolQ] + = True """ rules = { @@ -51,37 +59,10 @@ class ArrayQ(Builtin): def eval(self, expr, pattern, test, evaluation: Evaluation): "ArrayQ[expr_, pattern_, test_]" - pattern = Pattern.create(pattern) - - dims = [len(expr.get_elements())] # to ensure an atom is not an array - - def check(level, expr): - if not expr.has_form("List", None): - test_expr = Expression(test, expr) - if test_expr.evaluate(evaluation) != SymbolTrue: - return False - level_dim = None - else: - level_dim = len(expr.elements) - - if len(dims) > level: - if dims[level] != level_dim: - return False - else: - dims.append(level_dim) - if level_dim is not None: - for element in expr.elements: - if not check(level + 1, element): - return False - return True - - if not check(0, expr): - return SymbolFalse + if not isinstance(expr, Atom) and expr.head.sameQ(SymbolSparseArray): + return check_SparseArrayQ(expr, pattern, test, evaluation) - depth = len(dims) - 1 # None doesn't count - if not pattern.does_match(Integer(depth), evaluation): - return SymbolFalse - return SymbolTrue + return check_ArrayQ(expr, pattern, test, evaluation) class DisjointQ(Test): diff --git a/mathics/eval/testing_expressions.py b/mathics/eval/testing_expressions.py index 4046d0c8c..2bd751944 100644 --- a/mathics/eval/testing_expressions.py +++ b/mathics/eval/testing_expressions.py @@ -2,13 +2,15 @@ import sympy -from mathics.core.atoms import Complex, Integer0, Integer1, IntegerM1 +from mathics.core.atoms import Complex, Integer, Integer0, Integer1, IntegerM1 +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression -from mathics.core.systemsymbols import SymbolDirectedInfinity +from mathics.core.rules import Pattern +from mathics.core.symbols import SymbolFalse, SymbolTimes, SymbolTrue +from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolSparseArray def do_cmp(x1, x2) -> Optional[int]: - # don't attempt to compare complex numbers for x in (x1, x2): # TODO: Send message General::nord @@ -99,3 +101,64 @@ def expr_min(elements): def is_number(sympy_value) -> bool: return hasattr(sympy_value, "is_number") or isinstance(sympy_value, sympy.Float) + + +def check_ArrayQ(expr, pattern, test, evaluation: Evaluation): + "Check if expr is an Array which test yields true for each of its elements." + + pattern = Pattern.create(pattern) + + dims = [len(expr.get_elements())] # to ensure an atom is not an array + + def check(level, expr): + if not expr.has_form("List", None): + test_expr = Expression(test, expr) + if test_expr.evaluate(evaluation) != SymbolTrue: + return False + level_dim = None + else: + level_dim = len(expr.elements) + + if len(dims) > level: + if dims[level] != level_dim: + return False + else: + dims.append(level_dim) + if level_dim is not None: + for element in expr.elements: + if not check(level + 1, element): + return False + return True + + if not check(0, expr): + return SymbolFalse + + depth = len(dims) - 1 # None doesn't count + if not pattern.does_match(Integer(depth), evaluation): + return SymbolFalse + + return SymbolTrue + + +def check_SparseArrayQ(expr, pattern, test, evaluation: Evaluation): + "Check if expr is a SparseArray which test yields true for each of its elements." + + if not expr.head.sameQ(SymbolSparseArray): + return SymbolFalse + + pattern = Pattern.create(pattern) + dims, default_value, rules = expr.elements[1:] + if not pattern.does_match(Integer(len(dims.elements)), evaluation): + return SymbolFalse + + array_size = Expression(SymbolTimes, *dims.elements).evaluate(evaluation) + if array_size.value > len(rules.elements): # expr is not full + test_expr = Expression(test, default_value) # test default value + if test_expr.evaluate(evaluation) != SymbolTrue: + return SymbolFalse + for rule in rules.elements: + test_expr = Expression(test, rule.elements[-1]) + if test_expr.evaluate(evaluation) != SymbolTrue: + return SymbolFalse + + return SymbolTrue From a4d395d0a2b5604d007cadb4250e0a594dbf44e8 Mon Sep 17 00:00:00 2001 From: Li-Xiang-Ideal <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Tue, 12 Dec 2023 21:58:28 +0800 Subject: [PATCH 49/51] Undo check_SparseArrayQ --- .../builtin/testing_expressions/list_oriented.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/mathics/builtin/testing_expressions/list_oriented.py b/mathics/builtin/testing_expressions/list_oriented.py index cd643d618..d6e26a48a 100644 --- a/mathics/builtin/testing_expressions/list_oriented.py +++ b/mathics/builtin/testing_expressions/list_oriented.py @@ -8,9 +8,9 @@ from mathics.core.exceptions import InvalidLevelspecError from mathics.core.expression import Expression from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolSparseArray, SymbolSubsetQ +from mathics.core.systemsymbols import SymbolSubsetQ #, SymbolSparseArray from mathics.eval.parts import python_levelspec -from mathics.eval.testing_expressions import check_ArrayQ, check_SparseArrayQ +from mathics.eval.testing_expressions import check_ArrayQ #, check_SparseArrayQ class ArrayQ(Builtin): @@ -39,14 +39,6 @@ class ArrayQ(Builtin): = False >> ArrayQ[{{a, b}, {c, d}}, 2, SymbolQ] = True - >> ArrayQ[SparseArray[{{1, 2} -> a, {2, 1} -> b}]] - = True - >> ArrayQ[SparseArray[{{1, 2} -> a, {2, 1} -> b}], 1] - = False - >> ArrayQ[SparseArray[{{1, 2} -> a, {2, 1} -> b}], 2, SymbolQ] - = False - >> ArrayQ[SparseArray[{{1, 1} -> a, {1, 2} -> b}], 2, SymbolQ] - = True """ rules = { @@ -59,8 +51,8 @@ class ArrayQ(Builtin): def eval(self, expr, pattern, test, evaluation: Evaluation): "ArrayQ[expr_, pattern_, test_]" - if not isinstance(expr, Atom) and expr.head.sameQ(SymbolSparseArray): - return check_SparseArrayQ(expr, pattern, test, evaluation) + # if not isinstance(expr, Atom) and expr.head.sameQ(SymbolSparseArray): + # return check_SparseArrayQ(expr, pattern, test, evaluation) return check_ArrayQ(expr, pattern, test, evaluation) From 4692c5fb8d84e17addee4e11329d7ada4df3b9d7 Mon Sep 17 00:00:00 2001 From: Li-Xiang-Ideal <54926635+Li-Xiang-Ideal@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:07:36 +0800 Subject: [PATCH 50/51] Fix formatting --- mathics/builtin/testing_expressions/list_oriented.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/testing_expressions/list_oriented.py b/mathics/builtin/testing_expressions/list_oriented.py index d6e26a48a..b2f819331 100644 --- a/mathics/builtin/testing_expressions/list_oriented.py +++ b/mathics/builtin/testing_expressions/list_oriented.py @@ -8,9 +8,9 @@ from mathics.core.exceptions import InvalidLevelspecError from mathics.core.expression import Expression from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolSubsetQ #, SymbolSparseArray +from mathics.core.systemsymbols import SymbolSubsetQ # , SymbolSparseArray from mathics.eval.parts import python_levelspec -from mathics.eval.testing_expressions import check_ArrayQ #, check_SparseArrayQ +from mathics.eval.testing_expressions import check_ArrayQ # , check_SparseArrayQ class ArrayQ(Builtin): From 080a2fe6e924f6b1101f8f1f1ed80bc30f4617e0 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 13 Dec 2023 06:09:34 -0500 Subject: [PATCH 51/51] Add placeholder for 2024 roadmap --- FUTURE.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FUTURE.rst b/FUTURE.rst index e37758f22..291e016d2 100644 --- a/FUTURE.rst +++ b/FUTURE.rst @@ -2,9 +2,11 @@ .. contents:: -The following 2023 road map that appears the 6.0.0 hasn't gone through enough discussion. This provisional. -Check the github repository for updates. +2024 Roadmap +============ + +To be decided... 2023 Roadmap ============